APM

>Agent Skill

@facebook/iwsdk-ui-panel

skilldesign

Develop and iterate on IWSDK UI panels efficiently. Use when working on PanelUI components, debugging UI layout, or improving UI design in IWSDK applications.

typescript
apm::install
$apm install @facebook/iwsdk-ui-panel
apm::allowed-tools
Read, Edit, Write, Bash(npm *)
apm::skill.md
---
name: iwsdk-ui-panel
description: Develop and iterate on IWSDK UI panels efficiently. Use when working on PanelUI components, debugging UI layout, or improving UI design in IWSDK applications.
allowed-tools: Read, Edit, Write, Bash(npm *)
---

# IWSDK UI Panel Development Workflow

This skill teaches the efficient workflow for developing UI panels in IWSDK applications using temporary ScreenSpace positioning and backdrop techniques.

## Quick Iteration Workflow

When working on a UI panel, follow these steps for rapid iteration:

### 1. Add Temporary ScreenSpace Component

Temporarily add the `ScreenSpace` component to your PanelUI entity to make it fill the 2D screen during development:

```typescript
import { ScreenSpace } from '@iwsdk/core';

world
  .createTransformEntity(panelHolder)
  .addComponent(PanelUI, {
    config: '/ui/your-panel.json',
    maxWidth: 1.0,
    maxHeight: 0.5,
  })
  .addComponent(ScreenSpace, {
    width: '90vw', // Fill 90% of viewport width
    height: '90vh', // Fill 90% of viewport height
    top: '5vh', // Center with 5% margins
    left: '5vw',
  });
```

**Important:** This is temporary for development only. Remove before production.

### 2. Create a Clean Backdrop

Create a solid color backdrop far from your gameplay area for clean UI visibility:

```typescript
const backdrop = new Mesh(
  new BoxGeometry(20, 20, 0.1),
  new MeshBasicMaterial({ color: 0x1a1a2e }),
);
backdrop.position.set(0, 0, -50); // Far from gameplay
scene.add(backdrop);
```

### 3. Position Camera Close to Backdrop

Move the camera very close to the backdrop (within 0.5m) to eliminate background distractions:

```typescript
// Position camera very close to backdrop for clean UI development
camera.position.set(0, 0, -49.5); // Just 0.5m from backdrop at z=-50
camera.lookAt(0, 0, -50);
```

**Why close?** The backdrop must fill the entire field of view to block out the 3D scene. Being far away (50m) won't work - you'll still see the environment around the edges.

### 4. Iterate with Screenshots

Now you can rapidly iterate on your UI:

1. Make changes to your `.uikitml` file
2. Take a screenshot to see the result against a clean backdrop
3. The UI fills most of the screen, making it easy to see details like:
   - Border colors and thickness
   - Padding and spacing
   - Text alignment and sizing
   - Color contrast
   - Overall layout

The ScreenSpace component makes the panel "follow" the camera, so it appears as a 2D overlay on your backdrop.

### 5. Test in VR

When you enter VR mode:

- The ScreenSpace component automatically detaches the panel from the camera
- The panel returns to its original 3D world space position
- Your gameplay is unaffected

This dual-mode behavior is handled automatically by the `ScreenSpaceUISystem`.

## Understanding UIKit Size Signals

UIKit components expose size information through signals. Log these to debug layout issues:

```typescript
const document = PanelDocument.data.document[entity.index];

console.log('computedSize:', document.computedSize); // Intrinsic size in cm
console.log('targetSize:', document.targetSize); // Target size in meters
console.log('rootElement.size.value:', document.rootElement?.size?.value);
console.log('document.scale:', document.scale); // Applied scale
```

**Understanding the output:**

- `computedSize`: UIKit's rendered size in **centimeters** (based on your CSS)
- `targetSize`: The requested size in **meters** (from PanelUI maxWidth/maxHeight or ScreenSpace constraints)
- `document.scale`: Uniform scale factor applied to fit target while preserving aspect ratio

Example output:

```
computedSize: { width: 100, height: 50 }         // 100cm × 50cm
targetSize: { width: 0.274, height: 0.168 }      // 0.274m × 0.168m
document.scale: { x: 0.274, y: 0.274, z: 0.274 } // Scaled down by 0.274x
```

## ScreenSpace Component Reference

The ScreenSpace component positions panels using CSS-like properties:

```typescript
.addComponent(ScreenSpace, {
  width: '90vw',      // CSS size: px, vw, vh, %, auto
  height: '90vh',     // CSS size: px, vw, vh, %, auto
  top: '5vh',         // CSS position: px, %, vh, auto
  bottom: 'auto',     // CSS position: px, %, vh, auto
  left: '5vw',        // CSS position: px, %, vw, auto
  right: 'auto',      // CSS position: px, %, vw, auto
  zOffset: 0.2,       // Distance in meters from camera (default: 0.2m)
});
```

**How it works:**

- In desktop mode: Panel attaches to camera at `zOffset` distance, using CSS layout
- In VR mode: Panel detaches from camera, returns to world space position
- Automatic switching handled by `ScreenSpaceUISystem`

## Common Workflow Tips

### Centering Content

Use flexbox in your UIKitML for centered layouts:

```css
.container {
  display: flex;
  flex-direction: column; /* Stack vertically */
  justify-content: center; /* Center vertically */
  align-items: center; /* Center horizontally */
}
```

### Sharp Borders

Set `border-radius: 0` for square edges that align with grid systems:

```css
.panel {
  border-radius: 0; /* Square edges */
  border-width: 0.15;
  border-color: #27272a;
}
```

### UIKit Units

Remember: UIKit uses **centimeters** for sizing, world space uses **meters**:

- `width: 100` in UIKitML = 100cm = 1.0m
- `maxWidth: 1.0` in PanelUI = 1.0 meter

## Cleanup Before Production

Before committing or going to production:

1. **Remove ScreenSpace component** from your entity
2. **Remove or reposition backdrop** if not needed for gameplay
3. **Restore camera position** to gameplay view
4. **Remove debug logging** of size signals

The panel will remain at its world space position defined by the entity's transform.

## Example: Complete Development Setup

```typescript
// 1. Enable spatialUI feature
World.create(container, {
  features: { spatialUI: true },
}).then((world) => {
  const { scene, camera } = world;

  // 2. Create backdrop for UI development
  const backdrop = new Mesh(
    new BoxGeometry(20, 20, 0.1),
    new MeshBasicMaterial({ color: 0x1a1a2e }),
  );
  backdrop.position.set(0, 0, -50);
  scene.add(backdrop);

  // 3. Position camera close to backdrop
  camera.position.set(0, 0, -49.5);
  camera.lookAt(0, 0, -50);

  // 4. Create your UI panel with ScreenSpace
  const panelHolder = new Group();
  panelHolder.position.set(0, 1.5, -1.0); // World space position for VR
  scene.add(panelHolder);

  world
    .createTransformEntity(panelHolder)
    .addComponent(PanelUI, {
      config: '/ui/my-panel.json',
      maxWidth: 1.0,
      maxHeight: 0.5,
    })
    .addComponent(ScreenSpace, {
      // TEMPORARY for development
      width: '90vw',
      height: '90vh',
      top: '5vh',
      left: '5vw',
    });
});
```

## Troubleshooting

**Panel not filling screen:**

- Check ScreenSpace width/height values
- Verify UIKitML doesn't have fixed small dimensions

**Background still visible:**

- Camera too far from backdrop - move closer (within 0.5m)
- Backdrop too small - increase size to 20×20 or larger

**Panel doesn't return to world space in VR:**

- Verify `spatialUI: true` in World.create features
- Check that ScreenSpaceUISystem is running

**Size signals showing unexpected values:**

- UIKit uses cm, world space uses meters (100cm = 1m)
- Check if aspect ratio constraints are being applied