Luix v1.4.0 - The Visuals Update
This release adds three full-fledged visual editors (gradient,
sprite-rect, plus four hover previews), a new diagnostic, and a
sort-props action. Editors are marked Preview in the UI so
users know they're still being polished.
Gradient editor (luix.gradient.*)
A single Edit UIGradient CodeLens above every
e("UIGradient", { … }) element opens a combined side-panel editor
with Color, Transparency, and Rotation. Standalone
ColorSequence.new(...) and NumberSequence.new(...) literals (not
inside a UIGradient) get a focused per-literal editor instead.
- Colour ramp — drag triangle stops, click the strip to add,
per-stop colour picker, hover-indicator pill showing the offset. - Transparency curve — grid canvas with draggable circle stops
(X = time, Y = value), envelope shading when non-zero. - Rotation slider — −180° to 180°, slider + numeric input.
- Preview square — combines colour + transparency + rotation
and multiplies by the parent element'sBackgroundColor3
(Roblox'sUIGradientsemantics), so what you see matches what
Roblox renders. - Output respects
luix.color3.defaultFormatforfromRGB/
fromHex/new. DefaultColor = ColorSequence.new(white),
Transparency = NumberSequence.new(0), andRotation = 0are
omitted from the written-back props block — no noise. - Hover previews —
ColorSequenceshows the gradient strip,
NumberSequenceshows the value curve. Toggles:
luix.gradient.codeLensEnabled(on),
luix.gradient.previewOnHover(on). - Polish —
Shiftsnaps drag/click to 0.05, hover indicator
on strip and curve, scroll-wheel + arrow stepping on numeric
fields, decimal input always renders.regardless of locale,
blur-revert on invalid input.
Rect editor (luix.rectEditor.*)
An Edit sprite rect CodeLens appears above every
e("ImageLabel" | "ImageButton", { … }) whose Image prop is a
literal rbxassetid://…. Opens a side-panel editor:
- Thumbnail fetched from
thumbnails.roblox.comat the largest
available size, with a fallback ladder (768 → 512 → 420 → 256 → 150). - Draggable rectangle with 8 resize handles; dimmed mask shows
what gets cropped. - X / Y / W / H number fields with
Shift= 10× step. - Aspect-ratio auto-detect — reads a sibling
UIAspectRatioConstraintor a fixed-pixelSize = UDim2.fromOffset(…)
and pre-fills the Frame aspect input. - Crop preview overlay — a dashed yellow box inside the
selection showing exactly whatScaleType.Cropwill actually
render given the frame aspect. Hidden for other ScaleTypes. - Native dimension auto-detect via Open Cloud — when
luix.openCloud.apiKeyis set (key needs thelegacy-asset:manage
permission), the editor hits
apis.roblox.com/asset-delivery-api/v1/assetId/{id}, downloads the
returned CDN location, and reads the PNG/JPEG header to extract the
true pixel dimensions. One call per asset, ever — results are
persisted toglobalState. - Source dimensions fallback — without an API key (or if the
lookup fails) the editor uses the thumbnail's natural size and lets
you typeSource W/Source Hmanually. Manual values are also
cached per asset, so the typing cost is one-time. - Scroll-to-zoom — wheel on the canvas zooms (15% – 300%),
with a discoverable 🔍 zoom bar (− / % / +) in the corner. - Rect can exceed source dimensions — W and H accept up to 4×
source; the rect can be dragged past the image edge. Overflow is
signalled with a dashed amber border. - Stripped on the "full image" default —
ImageRectOffset = (0,0)
andImageRectSize = (0,0)are omitted on Apply.
Unused-prop diagnostic (luix.unusedProps.enabled, on by default)
Props declared in a component's parameter type
(props: { Foo: …, Bar: … }) or its @luix-props annotation that
are never read in the body are flagged with the unused-declaration
grey-out style (Hint severity, Unnecessary tag — same treatment
TypeScript uses for unused locals).
Skipped automatically when the body forwards props wholesale
(e(Base, props), for k, v in props do, computed-key indexing)
since static analysis can't determine downstream usage. Squiggle
lands on the field name inside the type annotation when possible,
otherwise on the function definition line.
Visual hover previews (luix.hoverPreviews.enabled, on by default)
Inline SVG previews rendered straight from your literal values —
no network requests, no caching:
TweenInfo.new(...)— 240×140 graph of the easing curve.
All 12EasingStyle× 4EasingDirectioncombinations are
implemented as math functions. Below the curve: duration,
repeat-count, reverses, and delay summary.e("UIPadding", { … })— box visualisation with the inner
content area indented by the configuredPaddingTop/Right/
Bottom/Left. Each non-zero side gets its pixel value labeled.e("UICorner", { … })— rounded rectangle rendered at the
configuredCornerRadius.e("UIStroke", { … })— sample box with the stroke applied
at the configuredThickness/Color/Transparency.
Sort props by category (luix.sortProps.*)
- Code action — Right-click anywhere inside a props table → 💡
Sort props by category. Reorders props by category (then by
canonical order within each category, stable on ties). - Format-on-save —
luix.sortProps.onSave(defaultfalse).
When enabled, every props table in the document is sorted on
save. Off by default so saving a teammate's file doesn't
reshape their layout. - Configurable order —
luix.sortProps.categoryOrder(string
array) lets you reorder or remove categories. Defaults:
Identification → Layout → Style → Visibility → Image → Text →
Behavior → Events → Refs → Children → Other. - Computed-key aware — captures
[React.Event.Activated],
[OnEvent "…"],[Children], etc. Vide-style plain identifier
events (Activated = function() … end) are recognised too. - Comment-safe — tables containing
--comments are skipped
so comments never get detached. Idempotent: re-sorting a sorted
table is a no-op (no spurious save edits).
Per-class prop type overrides
Some Roblox prop names mean different things on different classes
(e.g. Frame.Style → Enum.FrameStyle, but
GuiButton.Style → Enum.ButtonStyle). 1.3.0's global PROP_TYPES
map could only hold one value per prop name and silently picked
the wrong enum on Frame.
New PROP_TYPE_OVERRIDES map keyed by class, plus a
getPropType(className, propName) helper that walks the class
hierarchy. The hover tooltip, completion value-snippet, and the
wrong-enum diagnostic now all resolve Style (and any future
conflicts) per-class. Adding more is a one-line entry per class.
Wrap-in code action — fixes (regressions from 1.3.0)
- Multi-element selection — selecting
UIPadding + TextLabel
now wraps those two siblings in a new container, instead of
wrapping their parentFrame(the previous behaviour found the
smallest call containing the selection, which is always the
parent for multi-element selections). - Indentation — wrapped lines no longer get double-indented.
The previousindentLines(text, baseIndent + step)prepended
both the base AND the new step to lines that already had their
original indent baked in, producing 4-tab-deepName = …etc.
Expanded class & prop catalogue (src/data.ts)
Built-in classHierarchy and PROP_TYPES significantly expanded
to track newer Roblox additions and previously-missing props:
- GuiBase2d —
RootLocalizationTable,SelectionBehaviorDown/Left/Right/Up,SelectionGroup,SelectionChangedevent. - GuiObject —
InputSink,NextSelection*,SelectionImageObject. - GuiButton —
HoverHapticEffect,PressHapticEffect,Style. - Frame —
Style. - VideoFrame —
MaximumResolution,RollOffMaxDistance/MinDistance/Mode. - TextLabel / TextButton —
OpenTypeFeatures. - TextBox — re-rooted under
GuiObject(wasTextLabel) to
match Roblox's actual hierarchy; full text-prop set mirrored. - BillboardGui —
Adornee,ResetOnSpawn,TabKeyboardNavigation. - SurfaceGui —
Active,TabKeyboardNavigation. - ScreenGui —
TabKeyboardNavigation. - UIStroke —
BorderOffset,BorderStrokePosition,StrokeSizingMode,ZIndex. - Instance —
Archivable. - And 40+ new
PROP_TYPESentries for the above, covering
Rect,Camera,Player,LocalizationTable, plus several
new enum types (BorderStrokePosition,HapticEffect,
InputSink,NormalId,RollOffMode,SelectionBehavior,
StrokeSizingMode,VideoSampleSize).