Playground Widget Fixes
Problems
-
Widget renders on the viewport, not inside the demo frame —
position: fixedon body instead ofposition: absoluteinside the bordered "OSINT Analyzer Pro" container. Should feel like a publisher's app with the widget floating inside it. -
Layout width shifts — Page is narrow in default state, widens on session launch, and activity stream filter changes cause further width shifts. Should be a fixed full-width layout at all times.
-
Customize Widget controls are disconnected — Controls exist (position grid, fade, theme) but do nothing in the mock path because they target
sdkRef.current.updateSessionControls()which is null. Need to bind them to the actual rendered widget. -
Color picker is a plain text input — Should use a proper color picker component.
-
Items panel not wired — The SDK's
SessionHeadersupportsgetAvailableItems()and renders an items panel viatoggleItemsPanel(). The playground should show this working.
Fix 1: Scope widget inside demo container
Current: Widget is a position: fixed div appended to document.body.
Target: Widget is position: absolute inside the DemoArea container (which has position: relative).
Changes:
- In
index.tsxmock fallback: append widget to the demo container element (document.querySelector('[data-testid="playground-demo-area"]')) instead ofdocument.body - Change
position: fixedtoposition: absolute - DemoArea container already has
position: relative - Widget position values (top/bottom/left/right) map correctly within the container frame
- Ensure the demo area has a minimum height (400px+) so the widget has room to float
Fix 2: Fixed full-width layout
Current: Page width shifts between states.
Target: Consistent full-width layout (max-width: 1400px centered) in ALL states.
Changes in playground.module.css:
- Set
.playgroundContainertomax-width: 1400px; width: 100%; margin: 0 auto;regardless of session state - The two-column grid should ALWAYS render (left column shows placeholder in default, right column shows collapsed activity)
- Activity stream:
min-width: 0; overflow: hiddenso filter buttons don't cause width changes - Code panel at bottom: full width always, no reflow
Fix 3: Wire Customize Widget controls to the widget instance
Current: handleWidgetConfigChange calls sdkRef.current?.updateSessionControls() which is null in mock mode.
Target: Controls directly manipulate the widget DOM in mock mode via a useEffect on widgetConfig.
Changes in index.tsx:
useEffect(() => {
const widget = document.getElementById('gw-playground-widget');
if (!widget) return;
// Position
widget.style.top = widgetConfig.position.includes('top') ? '24px' : '';
widget.style.bottom = widgetConfig.position.includes('bottom') ? '24px' : '';
widget.style.left = widgetConfig.position.includes('left') ? '24px' : '';
widget.style.right = widgetConfig.position.includes('right') ? '24px' : '';
if (widgetConfig.position.includes('center')) {
widget.style.left = '50%';
widget.style.transform = 'translateX(-50%)';
} else {
widget.style.transform = '';
}
// Theme
widget.style.borderColor = widgetConfig.theme.primaryColor + '33';
widget.style.borderRadius = widgetConfig.theme.borderRadius;
const timer = document.getElementById('gw-playground-widget-timer');
if (timer) timer.style.color = widgetConfig.theme.primaryColor;
// Fade
if (widgetConfig.fade.enabled) {
widget.style.transition = 'opacity 0.3s';
widget.style.opacity = String(widgetConfig.fade.idleOpacity);
widget.onmouseenter = () => { widget.style.opacity = '1'; };
widget.onmouseleave = () => { widget.style.opacity = String(widgetConfig.fade.idleOpacity); };
} else {
widget.style.opacity = '1';
widget.onmouseenter = null;
widget.onmouseleave = null;
}
}, [widgetConfig]);
Fix 4: Real color picker
Current: Primary color is a text input with a tiny swatch.
Target: Use react-colorful (2.8KB, no dependencies, excellent dark mode support).
npm install react-colorful
In WidgetControls.tsx:
import { HexColorPicker } from 'react-colorful';
<HexColorPicker
color={config.theme.primaryColor}
onChange={(color) => onChange({...config, theme: {...config.theme, primaryColor: color}})}
/>
Style the picker to fit the dark theme controls panel (compact, 200px wide).
Fix 5: Items panel / purchasable items display
SDK support: SessionHeader.toggleItemsPanel() fetches GET /sdk/sessions/{id}/available-items and renders a dropdown listing items with token costs and "Buy" buttons.
For playground mock mode:
- Add a "Browse Items" button to the widget (alongside +5m button)
- Clicking shows a dropdown panel (positioned above the widget) listing mock items:
- Threat Intel Report — 150 SMART
- Dark Web Monitor — 75 SMART
- Entity Map — 125 SMART
- Clicking an item: deducts from balance, adds to purchase history, logs in activity stream
- Panel has dark styling matching the widget
For real SDK path (Lambda deployed):
- SDK's
SessionHeaderhandles this natively - Items configured for the playground demo app via admin API
Implementation Order
- Fix 2 (layout) — stabilizes the frame
- Fix 1 (scope widget) — moves widget inside demo container
- Fix 3 (wire controls) — makes customize panel functional
- Fix 4 (color picker) — polish
- Fix 5 (items) — adds items panel
Acceptance Criteria
- Page width is consistent in all states (default, active, ended)
- Widget renders inside the demo area container, not on the viewport
- Changing position in Customize Widget moves the widget inside the demo frame
- Changing primary color updates widget border and timer color live
- Changing border radius updates widget corners live
- Idle-fade toggle applies opacity to widget on mouse leave, full opacity on hover
- Color picker is a visual hex color picker (not text input)
- Items panel accessible from widget ("Browse Items" button)
- Purchasing an item updates balance and purchase history
- Activity stream filter buttons don't cause layout shifts
- Code panel reflects current customization choices
- All changes verified via Playwright screenshot comparison