Skip to main content

Playground Widget Fixes

Problems

  1. Widget renders on the viewport, not inside the demo frameposition: fixed on body instead of position: absolute inside the bordered "OSINT Analyzer Pro" container. Should feel like a publisher's app with the widget floating inside it.

  2. 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.

  3. 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.

  4. Color picker is a plain text input — Should use a proper color picker component.

  5. Items panel not wired — The SDK's SessionHeader supports getAvailableItems() and renders an items panel via toggleItemsPanel(). 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.tsx mock fallback: append widget to the demo container element (document.querySelector('[data-testid="playground-demo-area"]')) instead of document.body
  • Change position: fixed to position: 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 .playgroundContainer to max-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: hidden so 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 SessionHeader handles this natively
  • Items configured for the playground demo app via admin API

Implementation Order

  1. Fix 2 (layout) — stabilizes the frame
  2. Fix 1 (scope widget) — moves widget inside demo container
  3. Fix 3 (wire controls) — makes customize panel functional
  4. Fix 4 (color picker) — polish
  5. 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