feat(a2ui-playground): theme toggle, preview modes, and UI polish#2556
Conversation
… UI polish - Set page title to "Lynx A2UI Playground" (html.title + header brand) - Add dark/light mode toggle in the header - Simplify phone frame: clean bezel with border, no notch/status bar - Add Phone/Full preview mode toggle in the preview panel header - Fix preview header layout to handle chips + toggle without cramping
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds client-side light/dark theming (via ChangesTheme & Preview Mode
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Review rate limit: 3/8 reviews remaining, refill in 33 minutes and 32 seconds.Comment |
There was a problem hiding this comment.
Pull request overview
Adds UI theming controls and preview layout options to the a2ui-playground web app, improving the playground’s usability and presentation while keeping the existing render pipeline intact.
Changes:
- Introduces a header dark/light theme toggle implemented via a
data-themeattribute and updates branding/title to “Lynx A2UI Playground”. - Adds a Phone vs Full preview mode switch and corresponding full-iframe preview layout.
- Simplifies and polishes the phone frame visuals and preview panel header wrapping.
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 | Moves dark theme tokens to [data-theme="dark"], adds styling for theme toggle + preview mode switch, updates phone frame + preview layout styles. |
| packages/genui/a2ui-playground/src/pages/DemosPage.tsx | Adds preview mode state, segmented control UI, and conditional rendering for phone-framed vs full iframe preview. |
| packages/genui/a2ui-playground/src/components/MobilePreview.tsx | Simplifies markup to match the redesigned phone frame (no notch/status/home indicator). |
| packages/genui/a2ui-playground/src/App.tsx | Adds theme state + DOM data-theme wiring and updates header brand text + theme toggle button. |
| packages/genui/a2ui-playground/rsbuild.config.ts | Sets the HTML title to “Lynx A2UI Playground”. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| [data-theme="dark"] { | ||
| --geist-foreground: #ededed; | ||
| --geist-background: #0a0a0a; | ||
| --geist-secondary: #888; | ||
| --geist-border: #333; | ||
| --geist-border-hover: #555; |
| <div className='previewModeSwitch'> | ||
| <button | ||
| type='button' | ||
| className={previewMode === 'phone' | ||
| ? 'previewModeBtn active' | ||
| : 'previewModeBtn'} | ||
| onClick={() => setPreviewMode('phone')} |
| function getSystemTheme(): Theme { | ||
| return window.matchMedia('(prefers-color-scheme: dark)').matches | ||
| ? 'dark' | ||
| : 'light'; | ||
| } |
There was a problem hiding this comment.
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/rsbuild.config.ts (1)
11-35:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftPrefer a reliably reachable LAN address here.
findLocalIp()just takes the first non-internal IPv4, which can easily be a VPN, Docker, or virtual adapter. In those environments the generated__rspeedy_urlpoints phones at an unreachable host, so native preview QR flows break.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui-playground/rsbuild.config.ts` around lines 11 - 35, The current findLocalIp() returns the first non-internal IPv4 which can be a VPN/virtual adapter; change findLocalIp() to prefer a reliably reachable LAN address by: scanning all non-internal IPv4 addresses, filtering out interfaces whose names indicate virtual/VPN/docker adapters (e.g., containing "docker", "vbox", "vmnet", "veth", "tun", "tap", "vpn", "lo"), then choosing an address in private LAN ranges (10.x.x.x, 172.16-31.x.x, 192.168.x.x) before falling back to any remaining non-internal IPv4 and finally '127.0.0.1'; keep buildRspeedyBundleUrl(port) using the returned ip from findLocalIp(). Ensure you reference the existing function names findLocalIp and buildRspeedyBundleUrl when making the changes.
🧹 Nitpick comments (1)
packages/genui/a2ui-playground/src/pages/DemosPage.tsx (1)
378-399: ⚡ Quick winExpose the preview-mode selection to assistive tech.
The active Phone/Full state is only visual right now. Adding
aria-pressedto both buttons will make the current mode clear to screen readers without changing the interaction model.♻️ Minimal fix
<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>🤖 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 378 - 399, The Phone/Full buttons only convey state visually; update the two button elements in DemosPage.tsx (the ones using previewMode and setPreviewMode and className 'previewModeBtn') to expose state to assistive tech by adding aria-pressed attributes that evaluate to (previewMode === 'phone') for the Phone button and (previewMode === 'full') for the Full button so screen readers can detect the active mode while keeping the same click behavior.
🤖 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/App.tsx`:
- Around line 49-53: The theme is being applied in a useEffect which runs after
first paint causing a flash; change the App component to apply the theme before
paint by replacing useEffect with useLayoutEffect (import useLayoutEffect from
React) for the effect that sets
document.documentElement.setAttribute('data-theme', theme), or alternatively set
the data-theme synchronously during initialization using getSystemTheme before
React mounts; update the effect that references theme (and any references to
setTheme/getSystemTheme) so it uses useLayoutEffect to avoid the initial
light-flash.
---
Outside diff comments:
In `@packages/genui/a2ui-playground/rsbuild.config.ts`:
- Around line 11-35: The current findLocalIp() returns the first non-internal
IPv4 which can be a VPN/virtual adapter; change findLocalIp() to prefer a
reliably reachable LAN address by: scanning all non-internal IPv4 addresses,
filtering out interfaces whose names indicate virtual/VPN/docker adapters (e.g.,
containing "docker", "vbox", "vmnet", "veth", "tun", "tap", "vpn", "lo"), then
choosing an address in private LAN ranges (10.x.x.x, 172.16-31.x.x, 192.168.x.x)
before falling back to any remaining non-internal IPv4 and finally '127.0.0.1';
keep buildRspeedyBundleUrl(port) using the returned ip from findLocalIp().
Ensure you reference the existing function names findLocalIp and
buildRspeedyBundleUrl when making the changes.
---
Nitpick comments:
In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx`:
- Around line 378-399: The Phone/Full buttons only convey state visually; update
the two button elements in DemosPage.tsx (the ones using previewMode and
setPreviewMode and className 'previewModeBtn') to expose state to assistive tech
by adding aria-pressed attributes that evaluate to (previewMode === 'phone') for
the Phone button and (previewMode === 'full') for the Full button so screen
readers can detect the active mode while keeping the same click behavior.
🪄 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: 0156e095-6876-44e0-8e90-71ea3985f37f
📒 Files selected for processing (5)
packages/genui/a2ui-playground/rsbuild.config.tspackages/genui/a2ui-playground/src/App.tsxpackages/genui/a2ui-playground/src/components/MobilePreview.tsxpackages/genui/a2ui-playground/src/pages/DemosPage.tsxpackages/genui/a2ui-playground/src/styles.css
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
- Use useLayoutEffect for theme to prevent dark mode flash - Guard matchMedia for environments where it's unavailable - Remove dead .previewPanelMeta and .previewMetaTags CSS
Merging this PR will degrade performance by 77.4%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ⚡ | 008-many-use-state-destroyBackground |
9.5 ms | 8 ms | +18.84% |
| ❌ | basic-performance-large-css |
15.3 ms | 67.7 ms | -77.4% |
| ❌ | transform 1000 view elements |
40.1 ms | 46.9 ms | -14.56% |
Comparing Huxpro:Huxpro/a2ui-playground-improvements (aa3bb8b) with main (4fc29ee)
Footnotes
-
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. ↩
React Example (Element Template)#33 Bundle Size — 198.61KiB (0%).aa3bb8b(current) vs 4fc29ee main#31(baseline) Bundle metrics
Bundle size by type
|
| Current #33 |
Baseline #31 |
|
|---|---|---|
145.76KiB |
145.76KiB |
|
52.85KiB |
52.85KiB |
Bundle analysis report Branch Huxpro:Huxpro/a2ui-playground-im... Project dashboard
Generated by RelativeCI Documentation Report issue
React Example#7765 Bundle Size — 225.52KiB (0%).aa3bb8b(current) vs 4fc29ee main#7763(baseline) Bundle metrics
|
| Current #7765 |
Baseline #7763 |
|
|---|---|---|
0B |
0B |
|
0B |
0B |
|
0% |
0% |
|
0 |
0 |
|
4 |
4 |
|
180 |
180 |
|
69 |
69 |
|
44.54% |
44.54% |
|
2 |
2 |
|
0 |
0 |
Bundle size by type no changes
| Current #7765 |
Baseline #7763 |
|
|---|---|---|
145.76KiB |
145.76KiB |
|
79.77KiB |
79.77KiB |
Bundle analysis report Branch Huxpro:Huxpro/a2ui-playground-im... Project dashboard
Generated by RelativeCI Documentation Report issue
React MTF Example#897 Bundle Size — 196.68KiB (0%).aa3bb8b(current) vs 4fc29ee main#895(baseline) Bundle metrics
|
| Current #897 |
Baseline #895 |
|
|---|---|---|
0B |
0B |
|
0B |
0B |
|
0% |
0% |
|
0 |
0 |
|
3 |
3 |
|
174 |
174 |
|
66 |
66 |
|
44.05% |
44.05% |
|
2 |
2 |
|
0 |
0 |
Bundle size by type no changes
| Current #897 |
Baseline #895 |
|
|---|---|---|
111.23KiB |
111.23KiB |
|
85.45KiB |
85.45KiB |
Bundle analysis report Branch Huxpro:Huxpro/a2ui-playground-im... Project dashboard
Generated by RelativeCI Documentation Report issue
Web Explorer#9338 Bundle Size — 900.03KiB (0%).aa3bb8b(current) vs 4fc29ee main#9336(baseline) Bundle metrics
Bundle size by type
|
| Current #9338 |
Baseline #9336 |
|
|---|---|---|
495.9KiB |
495.9KiB |
|
401.92KiB |
401.92KiB |
|
2.22KiB |
2.22KiB |
Bundle analysis report Branch Huxpro:Huxpro/a2ui-playground-im... Project dashboard
Generated by RelativeCI Documentation Report issue
React External#880 Bundle Size — 680.82KiB (0%).aa3bb8b(current) vs 4fc29ee main#878(baseline) Bundle metrics
|
| Current #880 |
Baseline #878 |
|
|---|---|---|
0B |
0B |
|
0B |
0B |
|
0% |
0% |
|
0 |
0 |
|
3 |
3 |
|
17 |
17 |
|
5 |
5 |
|
8.59% |
8.59% |
|
0 |
0 |
|
0 |
0 |
Bundle analysis report Branch Huxpro:Huxpro/a2ui-playground-im... Project dashboard
Generated by RelativeCI Documentation Report issue
## 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 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## 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. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Summary
<title>and header brand)data-themeattribute instead ofprefers-color-schememedia queryTest plan
Summary by CodeRabbit
New Features
Style