Skip to content

chore: release v0.24.0 - #139

Open
github-actions[bot] wants to merge 1 commit into
mainfrom
tegami/version-packages
Open

chore: release v0.24.0#139
github-actions[bot] wants to merge 1 commit into
mainfrom
tegami/version-packages

Conversation

@github-actions

@github-actions github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

All bumped packages.

Package From To
@reactunity/create 0.23.2 0.24.0
@reactunity/material 0.23.2 0.24.0
@reactunity/renderer 0.23.2 0.24.0
@reactunity/scripts 0.23.2 0.24.0

Changelogs

2026-09-03-1f9ad3.md

Show Bumped Packages (1)
Package Bump
npm:@reactunity/renderer minor

Tailwind's Preflight, rewritten for ReactUnity

@reactunity/renderer/tailwind.css is what a ReactUnity app imports in place of Tailwind's
Preflight.
Preflight resets what a browser does to a document, and here most of it names elements
ReactUnity has not got while the rules that do land — * clearing every margin and padding — leave
ReactUnity's own control styling half undone. This is the equivalent written for what ReactUnity
renders: the padding and the grey fill on a button, an input and a toggle go, so p-* and
bg-* are the only things that decide, appearance stays so they still behave as controls, and the
root gets a line height and the font family ReactUnity actually ships. It also carries the two theme
values whose defaults are stacks of web font names, --font-sans and --font-mono.

An app still writes the one line Tailwind's docs give it. @reactunity/renderer/vite aliases a
bare @import "tailwindcss" to this file, so nothing about the setup changes:

@import "tailwindcss";

Only the bare specifier is aliased — tailwindcss/theme.css and the rest keep resolving to the
package, which is where this entry gets them from — and reactUnity({ tailwind: false }) turns the
alias off. Either half can also be imported alone, as
@reactunity/renderer/tailwind/preflight.css — into layer(base), since an unlayered rule beats
every utility — and @reactunity/renderer/tailwind/theme.css. The kitchen-sink sample now loads
Tailwind once for every page, Preflight included and the control look written back into its own
base layer through @apply, and
Using Tailwind CSS covers what does and does
not carry over.

2026-09-03-4ac1e7.md

Show Bumped Packages (1)
Package Bump
npm:@reactunity/renderer minor

CSS logical properties

padding-inline, margin-block, inset-inline-start and the rest of the logical box properties
now work
, as longhands, as the two-value shorthands, and under their camelCase names in an inline
style:

#panel {
  direction: rtl;
  padding-inline: 4px 12px;
  margin-block-start: 8px;
}

The inline axis is handed to Yoga's own start and end edges rather than resolved here, which is what
makes direction mean something: it is inherited, so setting it once turns the inline axis around
for a whole subtree, and an element needs no direction of its own to follow one set above it. The
block axis is always vertical — writing-mode does not exist here, so block-start is the top.

An inline value takes precedence over the physical property covering the same edge whichever order
the two are declared in. CSS would let the later declaration win; that is the one place the two
disagree, and the reason not to set both on one edge. Under UIToolkit the inline axis is always the
physical one, since UIElements has no start and end edge and ignores direction.

padding-start, margin-end and border-start-width — the React Native spellings that have been
in the property table all along — reach the same edges, and now do something for the first time:
nothing had been pushing them to Yoga.

The practical effect is that Tailwind's px-*, py-*, mx-*, my-*, inset-x-*, border-x-*,
ps-*, ms-*, start-* and space-x-* all work. v4 writes every two-axis and start/end utility
as a logical property, so those had been quietly doing nothing.

2026-09-03-7b3e41.md

Show Bumped Packages (1)
Package Bump
npm:@reactunity/renderer minor

A Vite preset for ReactUnity apps

@reactunity/renderer/vite configures Vite the way a ReactUnity app needs it, so a project's whole
config is:

import reactUnity from '@reactunity/renderer/vite';
import { defineConfig } from 'vite';

export default defineConfig({ plugins: [reactUnity()] });

Output goes into the Unity project. The preset walks up from the Vite root looking for an
Assets folder beside a ProjectSettings/ProjectVersion.txt, testing each ancestor and one level
below it, and builds into that project's Assets/Resources/react. Both layouts are found: the app
inside the project, and the app beside it. unityProject and assetPath say it outright when the
guess is wrong, and it stays out of the way entirely if the config already sets build.outDir.

The output folder is cleaned, and the .meta files survive. Unity writes a .meta beside every
asset, and the GUID in it is what scenes and prefabs reference — so emptying the folder the way Vite
would means every rebuild mints new GUIDs and breaks whatever pointed at the old ones. The clean
deletes the output and leaves the metas, and once the build has written its files it drops only the
metas whose asset did not come back, along with any directory the build left empty. A build that
fails leaves the metas alone, so the next successful one restores the files under their original
GUIDs. preserve exempts paths the build does not own, and the clean refuses outright to run on a
directory that is not plausibly build output — a package, a repository, an Assets folder, or
anything containing the Vite root.

For the same reason, output filenames carry no content hash by default: a hash renames the file on
every build, which costs it its meta. hashFileNames: true restores Vite's naming.

Emitted assets carry their extension twice, so index.css is written as index.css.css. Unity
finds a resource by its path minus the last extension, which makes assets/index.js and
assets/index.css the same resource -- and asking for the script could hand back the stylesheet, to
be run as JavaScript. Doubling puts the type back into the name the lookup uses. Content hashes did
this by accident before; without them it has to be deliberate.

Assets are never inlined. Vite turns a file under 4 kB into a data: URI, and Unity loads an
asset URL through its own loaders — so a small image that got inlined stopped resolving. The limit
is 0, which is where react-unity-scripts had it too.

CSS is emitted for an old browser. ReactUnity's CSS subset does not know oklch(), color-mix()
or nesting, and a stylesheet that uses them silently loses those declarations. build.cssTarget
defaults to chrome87, which is what makes Lightning CSS lower them on the way out — enough that
Tailwind v4's palette arrives as hex.

Vite's devtools and the HMR error overlay are off. Neither has anywhere to draw in Unity, and an
overlay rendered into a page nobody is looking at only hides the console message.

Everything the config sets itself wins — the preset only fills in what was left out — and
react: false drops @vitejs/plugin-react for a project that wants to configure React itself.

2026-09-05-9f2d4b.md

Show Bumped Packages (1)
Package Bump
npm:@reactunity/renderer minor

@starting-style, light-dark(), color-scheme, place-* shorthands, math functions, :nth-child(of S), attr(), linear() and boolean hover/pointer queries

@starting-style is the style an element has on the frame it appears, and the state a transition starts from. It used to collapse into the enclosing rule, so @starting-style { opacity: 0 } made the element invisible for good. Each rule in a block is now applied through :enter, which is the machinery ReactUnity already had for the first frame: .a { @starting-style { opacity: 0 } } is .a:enter { opacity: 0 }, and the top-level form works the same. The one visible deviation is that :enter adds pseudo-class specificity where the web adds none.

light-dark(<light>, <dark>) picks one of two values, wherever a color goes. The new color-scheme property (normal, light, dark, light dark; inherited) decides: a single scheme settles it for the subtree, anything else defers to the prefers-color-scheme media feature, and a change to that feature restyles the tree. color-scheme on the root element also seeds prefers-color-scheme, so @media (prefers-color-scheme: dark) rules follow the page's declared scheme; a value set through MediaProvider.SetValue is never seeded over, and withdrawing the declaration restores what the feature held. DefaultMediaProvider.SeedValue is the public shape of that.

place-content, place-items and place-self set the align and justify pair in one declaration, which Tailwind emits constantly. Yoga has no justify-items or justify-self, so the last two set their align property and only check that a second value is a keyword. The align and justify longhands also take the web spellings start, end, self-start, self-end, left, right and normal.

The math functions beyond min()/max()/clamp(): round() with nearest, up, down and to-zero, mod(), rem(), abs(), sign(), sin(), cos(), tan(), asin(), acos(), atan(), atan2(), pow(), sqrt(), hypot(), log() and exp(), plus the constants pi, e, infinity, -infinity and NaN. Operands are calc() expressions, so they nest in one another and in calc(), and take var(); all-constant inputs fold at parse time. The property's own converter reads the operands, so round(1250ms, 1s) is a duration, and when it refuses one they are all read as plain numbers, which is what calc(mod(7, 3) * 1s) needs; round(1s, 1) mixes the two and is invalid. Trigonometric arguments are angles or radians, the inverse functions return degrees. calc() with a leading sign, calc(-1 * 10px), used to fail to parse and now works.

:nth-child(An+B of S) and :nth-last-child(An+B of S) count only the siblings matching S, a selector list of compound selectors; a branch with a combinator is dropped, since siblings are what is counted. The clause adds the specificity of its most specific branch, as on the web.

attr() reads a data-* prop into any property: content: attr(data-label), with the prefix optional, a fallback as the second argument, and a unit after the name (attr(data-size px)) or a type for a typed value. A pseudo element reads the element it belongs to, and a number or boolean prop reads as its text.

linear() joins steps() and cubic-bezier() as an easing: linear(0, 0.25 40%, 1), with up to two positions per stop to hold a value, missing positions spread evenly, and extrapolation past the ends.

The hover, any-hover, pointer and any-pointer media features are now always defined, so the boolean forms (hover) and (pointer) are the standard way to gate hover-only styling; a platform whose real value was the provider's default used to leave them undefined. Two provider bugs went with it: a mobile platform registered the console media type instead of mobile, and the legacy Input path listed a touch screen as a fine pointer.

TypeScript's RenderStyle gains colorScheme, placeContent, placeItems and placeSelf, and the align and justify types take the new spellings.

2026-09-05-b7e3a1.md

Show Bumped Packages (1)
Package Bump
npm:@reactunity/renderer minor

Accept the CSS that Tailwind and the web actually emit

A stylesheet written for a browser, or generated by Tailwind v4, reaches ReactUnity full of spellings the engine parsed into nothing. Each one below was silently dropped before; each is now either honoured or mapped onto the nearest thing the renderer already had.

Names and keywords. Vendor prefixes are stripped, so -webkit-line-clamp is max-lines and -webkit-text-stroke is text-stroke (custom properties keep their --). text-decoration-line sets the same flags as font-style. overflow: auto scrolls and overflow: clip hides. font-variant: small-caps (and -caps, all-small-caps, petite-caps) is a text-transform. text-align takes justify, start and end; text-overflow takes clip. text-wrap: balance | pretty | stable parse as plain wrapping. The dvh/svh/lvh family, dvw/svw/lvw, their min/max forms and vi/vb all resolve as viewport lengths.

min(), max() and clamp(). Anywhere calc() works, with the same operand grammar, so clamp(1rem, 2vw + 0.5rem, 2rem) and min(100%, var(--width)) resolve against the same reference as calc. none is accepted for either bound of clamp, and all-constant inputs fold at parse time.

white-space. text-wrap: nowrap used to be the whole story; white-space now carries pre, pre-wrap, pre-line and break-spaces, and TextMeshPro's textWrappingMode follows it, preserving runs of spaces and newlines where the value asks for it. The renderer's whiteSpace typing grew the same values.

text-shadow. Rendered through TMP's underlay, so the shader draws it and no second text object exists. Pixel offsets and blur are converted to the underlay's own units from the font asset's sampling point size and gradient scale, which is why 1px 1px 2px looks the same at any font size. TMP has a single underlay, so only the first shadow of a list is drawn.

overflow-x and overflow-y. overflow is now a two-value shorthand over them. Yoga still has one overflow, so it gets the stronger of the two (scroll over hidden over visible), and the mask applies when either axis is hidden, because a rectangle mask cannot clip one axis alone. What the split does buy is scroll direction: a <scroll> with overflow-x: hidden scrolls vertically only, and overflow-y: hidden horizontally only, unless the direction prop says otherwise.

caret-color and ::selection. Both on <input>. The caret defaults to currentColor as the spec says. ::selection is a pseudo-element like ::placeholder, and its background-color is the TMP_InputField's selection highlight, so input:focus::selection { background-color: … } works too. That is the whole of what TextMeshPro can recolor: the selected glyphs keep their own color, and <text> has no selection at all.

scrollbar-color and scrollbar-width. scrollbar-color: <thumb> <track> and scrollbar-width: auto | thin | none | <length> on a <scroll>. They are implemented as custom properties the user-agent ::scrollbar and ::scrollbar-thumb rules read through var(), which gives the precedence the web has for free: a ::scrollbar rule of your own still wins, and auto falls back to the defaults. none is a zero-size scrollbar rather than display: none, because the ScrollRect toggles the scrollbar object itself to auto-hide it. One deviation: both inherit, where the spec inherits only the color.

Media queries. prefers-color-scheme and prefers-reduced-motion exist as features. The default scheme follows the Editor skin in the Editor and is light in a player; reduced motion defaults to no-preference. Both are runtime values, set with DefaultMediaProvider.SetValue from whatever the game reads from the platform.

Gradients. linear-gradient(to right in oklab, …), and the same hint on radial- and conic-gradient, parse. The colour space itself is not honoured; interpolation stays in sRGB, which is what a browser does for the default in srgb and is a close match for the short hues Tailwind emits.

A regression test pins down that Tailwind's filter: var(--tw-blur,) var(--tw-brightness,) … chains already worked: an empty var() fallback resolves to nothing and the remaining functions compose.

2026-09-05-e4c1a7.md

Show Bumped Packages (1)
Package Bump
npm:@reactunity/renderer minor

Container queries, style queries and container units

container-type (normal, size, inline-size), container-name and the container shorthand make an element a query container, and @container rules read the nearest eligible ancestor: (min-width: 400px), (width > 400px), (400px <= width < 800px), aspect-ratio, orientation, and/or/not, an optional container name, and nesting inside style rules, @media, @supports, @layer and other @container blocks. A length in a condition resolves against the container, so 20em is its font size.

style() queries compare a property of the container: a custom property as its declared text, with style(--theme) alone meaning it is set; a regular property as its computed value. Every element can answer one, whatever its container-type.

cqw, cqh, cqi, cqb, cqmin and cqmax measure the nearest size container's content box on each axis, and fall back to the viewport where there is none.

A container is measured after layout, and one whose content box changed since a query or a unit last read it restyles its subtree and lays out again in the same frame, up to twice. There is no size containment, so a container whose size depends on what the query changes can oscillate, and stops where the second pass lands. UIToolkit, whose layout is the framework's, sees a resize a frame later. A change to a property a style() query reads re-resolves the elements that read it, inherited or not.

The bundled parser reads a @container prelude as written, since a container query is not a media query, and parses one nested in a style rule the way it parses a nested @media.

TypeScript's RenderStyle gains containerType and containerName.

2026-09-06-a4c7e2.md

Show Bumped Packages (1)
Package Bump
npm:@reactunity/renderer minor

scrollbar-gutter, and scrollbar-size is gone

scrollbar-gutter: auto | stable | stable both-edges on a <scroll>. ReactUnity's scrollbars are drawn over the content, so there was never layout shift to prevent; what stable buys here is that content is never covered. The viewport shrinks by the bar's thickness along the bar's edge, for each direction the view can scroll in, and Yoga loses the same width, so a width: 100% child fits the visible area exactly and does not start a horizontal scroll of its own. both-edges mirrors the gutter onto the opposite edge. The thickness comes from the bar's own computed size, so scrollbar-width: thin reserves 6px and none reserves nothing. One limit: a percent padding on the scroll view cannot absorb the gutter, so the gutter is skipped there.

scrollbar-size, an alias of scrollbar-width that never existed on the web, is removed from the property map and the RenderStyle typing. scrollbar-width is the spelling.

2026-09-06-d2e7b9.md

Show Bumped Packages (1)
Package Bump
npm:@reactunity/renderer patch

light-dark() reaches Unity as written from a Vite build

The Vite plugin names an old browser as the CSS target so Lightning CSS lowers oklch, color-mix and nesting, which ReactUnity does not read. That target also lowered light-dark(), into two variables that only a color-scheme declaration in the same sheet defines, so an app's own light-dark() colors came out invalid unless a Tailwind scheme-* utility happened to sit next to them. The plugin now excludes that one feature from the lowering, since ReactUnity resolves light-dark() itself; an app that sets css.lightningcss.exclude keeps its own value.

Publish

The following packages will be published if merged:

Package Version Registry
@reactunity/create 0.24.0 npm
@reactunity/material 0.24.0 npm
@reactunity/renderer 0.24.0 npm
@reactunity/scripts 0.24.0 npm

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Unit Tests 6000.1.9f1

0 tests  ±0   0 ✅ ±0   0s ⏱️ ±0s
0 suites ±0   0 💤 ±0 
0 files   ±0   0 ❌ ±0 

Results for commit 5e06696. ± Comparison against base commit f785a0a.

@github-actions
github-actions Bot force-pushed the tegami/version-packages branch from 5e06696 to 2f2f8f7 Compare September 4, 2026 17:05
@github-actions
github-actions Bot force-pushed the tegami/version-packages branch from 2f2f8f7 to 3fe0bed Compare September 6, 2026 00:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants