# Scope and status

> What loom renders faithfully, what it recognizes but ignores, and what it will never be — read this before trusting a preview.

Source: https://docs.astra-void.xyz/loom/getting-started/scope-and-status/

Loom is a development tool. It renders your UI tree in a browser so you can iterate without a Studio
round-trip. Nothing it produces ships to Roblox, and its fidelity is deliberately partial.

> **Studio is still the source of truth**
>
> A preview is a high-fidelity approximation of Roblox layout and painting, not a reimplementation of
> the engine. Use it to iterate on structure, sizing, state and interaction. Verify final visuals in
> Studio before shipping.

## Implemented

| Area | Status |
| --- | --- |
| Scene IR + WASM layout + DOM renderer | Complete — the vertical slice all frontends share. |
| Roblox datatypes, `Instance` model, signals, services, scheduler | Complete enough to run real component libraries. [Attributes](https://docs.astra-void.xyz/loom/reference/supported-properties.md#attributes) since `0.11.0`. |
| [Luau standard library](https://docs.astra-void.xyz/loom/reference/luau-globals.md) | Complete as of `0.8.0` — `table`, `string`, `math`, `os`, `bit32`, `utf8`, `debug`, `buffer` and the free functions, installed before your entry. Metatables are the deliberate omission. |
| `@rbxts/react` adapter | Complete: a custom `react-reconciler` host config driving live loom instances. |
| Layout: list, grid, table, pages, padding, constraints, `AutomaticSize`, text measurement | Complete — every `UIGridStyleLayout` the engine has, as of `0.7.0`. |
| Visuals: text, `UICorner`, `UIStroke`, `UIShadow`, gradients, clipping, transparency, rotation, scrolling frames | Complete. |
| Images: every `ScaleType`, 9-slice, tiling, sprite windows, tints, `rbxassetid://` | Complete as of `0.7.0`, including asset ids in a static build — ids composed at runtime since `0.9.5`. |
| Text: `FontFace`, `RichText` markup, wrapping, `LineHeight` and measurement | Complete, and calibrated against the engine as of `0.9.5` — `TextSize` as a face height, half-pixel advances, kerning, one wrap authority for measurement and paint. |
| Fonts: the engine's typefaces, `registerFont` | The preview loads 28 of Roblox's families as of `0.9.0`, and names the rest; unbacked families warn, and since `0.9.6` a registered face that never arrived warns too. |
| Scrolling: `ScrollingFrame`, canvas growth, scroll bars | Complete as of `0.9.3` — before it, a canvas never outgrew its window and no bar was drawn. |
| [Debug panel](https://docs.astra-void.xyz/loom/guides/debug-panel.md) | As of `0.10.0` — timings, viewport, live tree, loaded typefaces, and a stage inspector. Off unless asked for. |
| Flex: `UIListLayout` flex and `Wraps`, `UIFlexItem` | Complete. |
| `TweenService`, `TextService`, `Debris`, bindings, `@rbxts/ripple` | Complete. |
| Pointer and keyboard input, focus, `UserInputService` | Complete, with Roblox input-sinking semantics. |
| Dev loop: Vite plugin, HMR, `loom preview`, `loom build` | Complete. |
| `vide` adapter | Shipped, on the same Scene IR. |

Those rows are area-level. For the per-property answer — is `AutomaticCanvasSize` honored, does
`UIGradient.Rotation` work, what happens to `ScrollBarThickness` — see [Supported instances and
properties](https://docs.astra-void.xyz/loom/reference/supported-properties.md).

The practical proof point: a full headless component library — dialogs, popovers, comboboxes,
selects, tabs, sliders, focus scopes, portals and layers — renders and behaves correctly under `loom
preview`, across more than fifty scenes, without a single source change on the library's side.

## Not implemented

**`UIScale`.** A valid JSX intrinsic with typed props, and a no-op: nothing reads `UIScale.Scale`, so
a scaled subtree renders at 1×. (`UIFlexItem`, which used to sit in this row, is implemented as of
`0.5.0`.)

**`TextScaled`.** Accepted on every text class and never applied — text renders at `TextSize`
whether or not it fits.

**`Gotham` and `BuilderSans`, out of the box.** Roblox's default family, and the Builder faces behind
it today, are proprietary and cannot be redistributed, so the preview loads only the openly licensed
families. Both are still *named*, so they resolve to a stack and warn rather than drifting silently,
and a project that has the files registers them itself — see [Fonts and text
metrics](https://docs.astra-void.xyz/loom/guides/fonts.md).

**Metatables.** `setmetatable` / `getmetatable` / `newproxy` are not installed. Loom runs the
author's TypeScript, whose classes are JavaScript classes, and there is no faithful way to give a
plain object a metatable's `__index` behaviour without proxying every table in the program.

**A roblox-ts compiler transform.** The preview path deliberately skips `rbxtsc` and lets esbuild
transpile. A standalone transform, which would let loom consume compiled output rather than source,
is still open.

**A Luau adapter.** The IR was designed so a Luau frontend can be added. It has not been.

## Things that are true, not bugs

- **A list can flow alphabetically.** `SortOrder` defaults to `Enum.SortOrder.Name` on every layout,
  as it does in Studio, so children with distinct `Name`s sort by name rather than by source order.
  Loom defaulted to `LayoutOrder` until `0.7.0`; a list that reordered on upgrade was already
  ordering that way in the engine. Set `SortOrder={Enum.SortOrder.LayoutOrder}` for the old
  behaviour.
- **No typechecking.** esbuild strips types; a preview renders whether or not your project typechecks.
  See [TypeScript setup](https://docs.astra-void.xyz/loom/guides/typescript-setup.md).
- **`print` types oddly.** The globals declaration deliberately omits `print` because `lib.dom`
  already declares one and redeclaring is a compile error. The runtime still overwrites the value.
- **Unknown services are stubs.** `@rbxts/services` is aliased to a module exporting only the
  [services the runtime actually implements](https://docs.astra-void.xyz/loom/reference/services.md) — the behavioural ones
  (`CollectionService`, `ContextActionService`, `Debris`, `GuiService`, `HttpService`, `Players`,
  `RunService`, `StarterGui`, `TextService`, `TweenService`, `UserInputService`, `Workspace`) plus
  the container-only ones (`ReplicatedStorage`, `Lighting`, `SoundService`, …). Importing anything
  else is a build error rather than a silent stub, which is the intended trade.
- **A Luau-only package needs a shim.** Most roblox-ts packages redirect to their own TypeScript
  source automatically; a declaration-only one has no source to redirect to and fails by name until
  you point [`shims`](https://docs.astra-void.xyz/loom/guides/package-compatibility.md#shims) at a browser module. `@rbxts/react`,
  `@rbxts/react-roblox`, `@rbxts/services`, `@rbxts/vide`, `@rbxts/ui-labs`, `@rbxts/ripple` and
  `@rbxts/react-ripple` are handled out of the box.
- **`rbxassetid://` is resolved for you, twice over.** Roblox's thumbnail API sends no CORS headers,
  so a dev-server route resolves the id under `loom preview`, the embedded server and Next dev. A
  [static build](https://docs.astra-void.xyz/loom/guides/static-builds-and-embedding.md#rbxassetid-in-a-static-build) has no server
  to ask, so as of `0.7.0` the ids are resolved at build time and the images are written into the
  output — read out of the bundle, and, since `0.9.5`, by mounting each gallery target and reading
  the tree, which is what finds an id composed at runtime. An image the first render never reaches
  still needs a real URL, or your own `setImageResolver`.
- **The React version is pinned in effect.** The adapter's reconciler targets React 18; the plugin
  aliases bare `react` to one absolute path to guarantee a single instance.
- **Text metrics are the browser's, corrected toward the engine's.** Loom measures with canvas
  `measureText()`, so shaping and hinting are not Roblox's. Three things narrow the gap: the preview
  loads the right typeface itself as of `0.9.0`, `TextSize` is divided by the face box rather than
  spent as a `font-size` as of `0.9.3`, and advances are quantized to the half pixel and kerned the
  way the engine spends them as of `0.9.4`–`0.9.5`. Against `GetTextBoundsAsync` at 50 widths, the
  wrapped line count now matches 49 times; what is left is a measurement about a percent roomy, so
  text wraps a hair early rather than overflowing. [The detail](https://docs.astra-void.xyz/loom/guides/fonts.md#where-a-line-breaks).

## Versioning

All `@loom-dev/*` packages release in lockstep — the changesets configuration marks them `fixed`, so
a bump to one bumps them all. Mismatched versions across the scope are never a supported combination.
The project is pre-1.0: expect the plugin's config contributions and the gallery URL contract to be
the stable-ish parts, and internal package boundaries to move.
