IWSDK UI
Deliver the requested UI as one verified vertical slice. Use the current request from conversation context; do not ask the user to repeat it or create a separate design document.
Choose the required surface
Before editing, classify the request:
- UIKitML only: document layout, styling, fonts, or reusable controls;
- spatial panel: UIKitML plus manifest registration and a scene instance;
- runtime UI: either of the above plus element lookup, events, and state;
- ScreenSpace HUD: browser-camera UI that intentionally returns to its authored world transform in immersive XR.
Do not add placement, ScreenSpace, runtime state, or camera changes when the request does not require them.
Fast path
Inspect the relevant UI, asset manifest, active scene, and direct runtime caller
with one shell command that prints those files; do not issue a separate Read
tool call for every file. Then make the first coherent edit before broad
reference or dependency discovery. Use focused IWSDK reference queries only for
a concrete unsupported element, property, or runtime API question. Do not open,
grep, or list node_modules at any point in this workflow.
Strict image budget: during implementation, do not open any PNG, JPEG, or WebP
with the Read tool. Finish the runtime checks first. At the final visual gate,
create and open exactly one compact artifacts/ui-runtime-review.jpg contact
sheet as described below.
Keep the three stable identities explicit:
- manifest asset ID;
- scene node ID for a placed instance;
- UIKitML element IDs used by application code.
Register each file under public/ui/ as AssetType.UIKitML in the configured
asset manifest. UIKitML is the source of truth; do not generate an intermediate
JSON representation.
Author the complete first pass
- UIKit numeric dimensions are centimeters (
100is one meter). - Use stable
idattributes for every element code must address. - Prefer supported explicit properties over CSS shorthand.
- Reuse the configured Horizon kit and bundled Lucide icons instead of registering replacements in application code.
- In the stock Horizon kit, assume
<Panel>renders an opaque light surface; start with dark primary and secondary text unless a preview proves otherwise. - Remote fonts can reflow layout. Use a local font when offline availability is a product requirement.
- Use plain ASCII punctuation in runtime status copy. Do not use em dashes, smart quotes, or other typographic punctuation unless the chosen local font has already rendered that exact glyph successfully.
For a scene-authored instance, use asset content and an explicit transform. In
the stock starter composition, a front-facing UIKitML panel at negative Z faces
the default player with identity rotation. Do not add rotationDeg: [0, 180, 0]
unless the existing app establishes that convention or a measured render proves
it necessary.
The default XR player and an authored non-XR hero camera can be on opposite
sides of a single-sided panel. Preserve the transform required by the requested
player viewpoint; if an authored evidence camera sees the back face, move or aim
that camera instead of rotating the product UI away from the player.
When adding UI to an established composition, preserve existing primary UI and
objects. Start from the app's scale and spacing. In the stock scaffold, use
roughly 0.25-0.32 scale for a secondary panel and keep a same-band panel at
least 0.75 world meters above the existing central panel, at the same or
greater depth. Prefer a centered safe region over fragile frame edges.
Connect runtime behavior
Resolve a scene-authored panel by scene node ID:
const panel = world.requireSceneObject<UIKitMLAsset>('PanelNode');
const status = panel.requireElementById<UIKit.Text>('StatusText');
const action = panel.requireElementById('Btn_Action');
status.name = 'StatusText';
action.name = 'Btn_Action';
action.addEventListener('click', onAction);
cleanupFuncs.push(() => action.removeEventListener('click', onAction));
Assign exact runtime names when the request requires controls to be addressable. Register listener cleanup. Do not locate panels by transient ECS index, manifest URL, or internal document implementation details.
For a small scene-authored state machine, use the public ECS surface directly;
do not inspect node_modules to rediscover these contracts:
export const PanelState = createComponent('PanelState', {
step: { type: Types.Int8, default: 0 },
complete: { type: Types.Boolean, default: false },
});
export default defineComponents([PanelState]);
class PanelSystem extends createSystem({
panel: { required: [PanelState] },
activated: { required: [ActionMarker, Pressed] },
}) {
init(): void {
this.queries.activated.subscribe('qualify', () => this.advance());
}
private advance(): void {
const panel = this.queries.panel.entities.values().next().value;
if (panel == null) return;
// Read and write scalar fields with getValue/setValue, then update UIKit.
}
}
Query result entities is a Set; use iteration or
entities.values().next().value, never array indexing. @iwsdk/core re-exports
the Three.js types needed for visual feedback, and scene-authored systems may use
Pressed qualification plus the existing app's marker component. Clone shared
asset materials before per-instance color or emissive changes.
Imported models can use material arrays or emissive-capable material subclasses
that do not satisfy a narrow instanceof MeshStandardMaterial check. For an
explicit whole-body state tint, traverse every Mesh, normalize its material to
an array, clone each material with emissive and emissiveIntensity, assign the
clones back, and update all of them. If an existing emissiveMap masks the
requested solid diagnostic color, clear that map on the clones and set
needsUpdate; do not alter shared source materials.
object3D.traverse((child) => {
if (!(child instanceof Mesh)) return;
const source = Array.isArray(child.material) ? child.material : [child.material];
const clones = source.map((material) => material.clone());
child.material = Array.isArray(child.material) ? clones : clones[0];
for (const material of clones) {
if ('emissive' in material && 'emissiveIntensity' in material) {
const stateMaterial = material as MeshStandardMaterial;
stateMaterial.emissiveMap = null;
stateMaterial.needsUpdate = true;
stateMaterials.push(stateMaterial);
}
}
});
These are known public top-level exports: createComponent, createSystem,
defineComponents, Types, Entity, Pressed, UIKitMLAsset, UIKit,
Color, Mesh, MeshStandardMaterial, and Object3D. Import them directly
from @iwsdk/core. Typecheck the implementation; if an import fails, use the
compiler error or one focused IWSDK reference query. Do not inspect package
source, declarations, exports, or directories to reconfirm the export chain.
For immersive XR, default to a world-space panel or attach contextual UI to the
object it controls. Use a thresholded Follower targeting world.player.head
only when compact global UI must remain discoverable. Do not head-lock menus,
reading surfaces, or persistent panels; reserve direct world.playerHeadEntity
parenting for tiny, transient, non-interactive markers that require exact view
alignment. Do not use ScreenSpace expecting it to remain camera-attached in
XR. See https://iwsdk.dev/concepts/spatial-ui/hud.html for the placement
guidance.
Use ScreenSpace only for a real HUD requirement. Its dimensions and offsets are
CSS strings. Preserve the authored immersive transform and verify browser and XR
views independently when both modes matter.
Verify with a bounded loop
Typecheck after the complete first slice, then start or reuse one managed
session. If no command-ready session exists, run npx iwsdk dev up --open
once. Do not call dev --help, separately call dev open, restart a healthy
session, start a second browser, or build a custom UIKit renderer.
Run
npx iwsdk ui assets --rawonce to confirm registration.Render one isolated preview after the first complete layout:
npx iwsdk ui render-preview \ --input-json '{"assetId":"panel-id","width":800,"height":600}' \ --output-file artifacts/panel-preview.pngAlways provide
--output-file; do not accept a temporary screenshot path and add a separate copy step.If spatial placement matters, render the authored hero view once; use
viewonly for built-in presets:npx iwsdk scene render-file \ --input-json '{"path":"public/scenes/main.iwsdk.scene.json","viewId":"hero"}' \ --output-file artifacts/scene-hero.pngKeep the full render result from this call so
visibleNodeIds, validity, framing, and the PNG are checked together. Do not pipe it throughhead/tail/grepand rerun only to recover discarded metadata.Exercise each required control through real pointer or XR input and query the named state or live element needed to prove the result.
Capture one final runtime preview from the intended viewpoint. Reuse a required completion-state capture when it already provides that evidence.
For common CLI verification, use these forms directly rather than discovery:
npx iwsdk xr status
npx iwsdk xr enter --input-json '{}'
npx iwsdk xr look-at --input-json '{"device":"controller-right","target":{"x":0,"y":1.5,"z":-2}}'
npx iwsdk xr select --input-json '{"device":"controller-right","duration":0.2}'
npx iwsdk ecs find --input-json '{"withComponents":["ComponentName"]}'
npx iwsdk ecs query --input-json '{"entityIndex":12}'
npx iwsdk browser logs --input-json '{"count":30}'
npx iwsdk browser reload --input-json '{}'
For a file capture, use npx iwsdk browser screenshot --output-file <path>;
browser_screenshot does not accept an outputPath input field.
When a UI change requires a regression smoke for an existing
DistanceGrabbable, do not substitute a long xr select click. Aim at the
object, hold select with
npx iwsdk xr set-select-value --input-json '{"device":"controller-right","value":1}',
query the entity while held to observe Grabbed, then release with the same
command and "value":0. Movement is unnecessary unless the request asks for
it.
The commands in this section are the supported path. Do not call broad
iwsdk, ui, browser, ecs, or xr help. If a shown command fails, use its
error output and the exact forms above to correct it instead of invoking
--help.
Batch layout fixes before rerendering. By default allow one isolated preview, one authored-scene render, and one final runtime capture; add one replacement preview only to verify a focused correction. Do not rerender after every small edit, repeatedly restart the runtime, or use screenshots to debug input state. Keep the whole task under roughly 80 tool calls by batching independent reads and related state queries. Check console logs once after final behavior unless a new edit creates a concrete reason to check again. Do not capture extra diagnostic screenshots for states already proven by named queries. For the final visual comparison, create one compact JPEG contact sheet with the supplied baseline above the final live-runtime capture, then load only that one image into model context. Do not load the full-size PNGs separately. On Windows, substitute the two paths in this known command:
powershell.exe -NoProfile -Command 'Add-Type -AssemblyName System.Drawing; $a=[Drawing.Image]::FromFile((Resolve-Path "BASELINE.png")); $b=[Drawing.Image]::FromFile((Resolve-Path "FINAL.png")); $w=600; $ha=[int]($a.Height*$w/$a.Width); $hb=[int]($b.Height*$w/$b.Width); $o=[Drawing.Bitmap]::new($w,$ha+$hb); $g=[Drawing.Graphics]::FromImage($o); $g.DrawImage($a,0,0,$w,$ha); $g.DrawImage($b,0,$ha,$w,$hb); $o.Save((Join-Path (Get-Location) "artifacts/ui-runtime-review.jpg"),[Drawing.Imaging.ImageFormat]::Jpeg); $g.Dispose(); $o.Dispose(); $a.Dispose(); $b.Dispose()'
artifacts/ui-runtime-review.jpg is the only image file to open with the Read
tool. Do not open isolated previews, hero renders, progress captures, completion
captures, the baseline PNG, or the final PNG separately.
Treat that contact sheet as a bounded layout sanity check, not a request to visually prove every state detail. If it shows no blocking overlap, clipping, unreadable primary UI, or accidental scene replacement, stop visual inspection. Do not create follow-up zooms, crops, or object close-ups. Use the named ECS state queries to prove interaction feedback; the evaluator will inspect the saved progress and completion artifacts.
Render isolated, hero, progress, completion, and invalid-action files when the task requires them, but do not open those files in model context; use render metadata, named state queries, and file existence for those checks. After a context compaction, continue from the measured state instead of rediscovering package exports, CLI command families, or already accepted visual framing.
The final visual check must confirm:
- the complete panel is inside the expected frame;
- important text and controls remain readable at a compact preview size;
- primary text has clear contrast against the actual rendered surface;
- new UI does not touch or obscure existing primary UI or objects;
- required states differ through a coarse visible treatment, not only fine text. When the task explicitly asks for visually distinct progress/completion or success/error states, change at least a quarter of the panel surface (for example a substantial header block or whole-surface tint) and/or a major scene object's whole-body emissive treatment. A thin accent bar, small chips, icons, and copy changes alone are not sufficient evidence.
Finish with a passing typecheck and production build, no UIKit parser/resource errors, and concise runtime evidence. Remove temporary preview files or camera workarounds that are not deliverables.