Release Notes - MeoNode UI
This page is generated from the latest GitHub Releases.
3.1.3 (2026-09-27)
Patch Changes
-
#47
c3abb1bThanks @l7aromeo! - A slow page no longer loses the server HTML of its Suspense boundaries whenThemeProviderhydrates (#46).The provider adopted the reader's stored mode by setting state as it hydrated, which changed its context value. React discards the server HTML of any Suspense boundary below a changed context that has not hydrated yet, and renders it again in the browser: on a slow load, content the reader was already looking at, or scrolled to through a
#fragmentlink, was replaced, losing its scroll position and state, and content the server was still streaming was thrown away. The mode now lives outside the context: eachuseThemereader, and each element whosecssholds a theme function, takes the reader's mode as it hydrates, before paint, and nothing else re-renders. On a page rendered only in the browser, they have the reader's mode from their first render.PortalProviderhad the same problem when a layer was opened while the page was still hydrating, from a mount effect for instance: opening one now re-rendersPortalHostalone.A
csstheme function readingtheme.modeinside a boundary that hydrates after the provider now shows the reader's mode. It kept the default mode's style until something else re-rendered it.
3.1.2 (2026-09-27)
Patch Changes
-
#43
6ade6d4Thanks @l7aromeo! - Server memory no longer grows with every distinct dynamic style a process renders.On the server, Emotion keeps each rule it compiles in a memo that lasts as long as the process, shared by every cache built on its default plugins. A long-running server rendering styles that vary per request, such as colours or sizes computed from data, grew by roughly 330 to 860 MiB per million distinct styles and never gave the memory back. The caches meonode creates on the server now keep that memo with the render that uses it: the React Server Components request cache, the
StyleRegistrycache, and the cache a styled element outside anyCacheProvidergets, where a bounded memo shared across renders keeps repeated styles fast. The rules, class names and markup are byte for byte what they were.A
csskey whose at-rule condition or selector holds a theme token now gets the same vendor prefixes as the rest of the page.
3.1.1 (2026-09-27)
Patch Changes
-
#41
333a92eThanks @l7aromeo! - Theme tokens in at-rule conditions and selectors resolve again in server components (#39).A node rendered from a server component, whose
cssput a theme token in a key such as'@media (width >= theme.breakpoint.wide)', got a rule with the token still in its condition. The browser drops such a rule, so every style behind a themed breakpoint stopped applying. A condition or selector needs the theme's concrete value, not a CSS variable, and a server component cannot read theThemeProviderabove it. That part of the node'scssis now written where the theme is, in the client tree, for the class the server gave the element, so the element stays the one element it was and the rest of itscssis still compiled on the server. This covers host tags and function components alike, conditions nested inside selectors,csswritten as a string, where a token in a block's condition or selector is resolved the same way, and a class carrying such a key that is handed to another styled element, including across a'use cache'boundary. A selector naming a class or id that merely containstheme., such as'& .theme.accent', is left as written.A key with a theme token now keeps its place in the
cssobject when it is resolved. It used to move after the entries written below it. Visible change: a themed at-rule or selector that conflicts with a block written after it now loses to that block, as the written order says it should; before, it won.A key whose token cannot be resolved, because no
ThemeProvideris above the node or its theme has no such value, is left out rather than written with the token, in server and client components alike, and in development a warning names the key.
3.1.0 (2026-09-26)
Deprecated. In server components, 3.1.0 drops or breaks a
csskey whose at-rule condition or selector uses a theme token (#39). Upgrade to 3.1.2 or later.
Minor Changes
-
#36
af53324Thanks @l7aromeo! - React Server Components: every server-compiled style now reaches the page, and host tags no longer ship theircssin the flight payload.-
Rules reach prerendered pages (#34). A node whose
cssis compiled on the server into a class name got its class but not its rule when the page was statically prerendered, so it rendered unstyled. The rule now travels with the render that compiled it, as a<style href precedence="meonode">React hoists into the document head, so it arrives however the page is rendered: prerendered, streamed, or cached. -
createNode(next/link)from a server component gets its rule (#32). The link had its class and the browser's default link styles. -
Each request keeps its own rules. Server-compiled rules were queued in one process-wide list and flushed by whichever render drained it next, so a rule could land in another request's HTML, two requests rendering at once could each take part of the other's rules, and a prerendered page could carry the next page's rules instead of its own.
-
Host tags compile on the server (#34). A
Div,Span,Button, … with styles in a server component was rendered as a client component carrying its wholecssobject, so the styles reached the browser twice. It now renders as the element itself with a server-compiled class.:hover, media queries and theme tokens behave as before. A 200-row page's flight payload drops from 55.1 KB to 24.5 KB. Host tags inside'use client'components are unchanged. -
A
classNamepassed into a styled server component overrides its owncss, composed into one class as Emotion does on the client. Before, the element kept both classes and the winner depended on the order the rules reached the page. Classes meonode did not generate, such as utility classes, are kept as they are. This holds across a'use cache'boundary too: a class handed into a cached component is looked up in a bounded store of classes compiled for components (the most recently used 10,000 classes, at most 4 MiB of style text). A class that has left the store stays a separate class, and is reported once in development. -
A render stays one element. The
<style>elements go into the children of the topmost host element above the node that uses them, after its own children, so the root of.render()is the element it would be without styles: a cloning parent (Slot, RadixasChild,Children.only) still receives it, and reordering siblings does not remount them. When nothing in a render can hold its rules, because the root is a void element such asImg, ansvg, or a component with no host above the styled node, the root is returned as a Fragment of the element and its rules, and a cloning parent receives that Fragment. Pass the node rather than its rendered element to avoid it:const Link = createNode(NextLink) Node(Slot, { children: Link({ href: '/', color: 'red' }) }) // cloned Node(Slot, { children: Link({ href: '/', color: 'red' }).render() }) // Fragment -
No crash on unrecognised children. The
react-serverbuild of React exports noComponent, and checkinginstanceof React.ComponentthrewTypeError: Right-hand side of 'instanceof' is not an objectfor any child that reached it, including aPromise. Such children are now handed to React as written.
-
Patch Changes
-
#36
af53324Thanks @l7aromeo! - Thecssprop: a call site extends a factory'scss, every shape the type accepts renders, and theme values resolve the same on server and client.-
A call-site
cssextends a factory's instead of replacing it (#31).createNodeandcreateChildrenFirstNodecombined props with a shallow spread, so a call site that added one rule dropped every pseudo-class, media query and@supportsfallback the factory defined. Two maps now merge key by key, recursing into nested selectors and at-rules, with the call site winning a conflict; an explicitundefinedstill drops one factory rule. When either side is an Emotioncss()result, an array, a function or a string, the two are composed as[factoryCss, callSiteCss], so both apply and the call site wins.css: falseornullat the call site still replaces the factory's.const Card = createNode('div', { padding: 24, css: { '&:hover': { color: 'blue' } } }) Card({ css: { margin: 4 } }) // keeps :hover -
Non-object
cssrenders. It was spread into one object with the flat CSS props, which is only right for a map: an array became rules keyed by index (.css-x 0{…}, never matching), a string one declaration per character, a function vanished, and acss()result dropped the flat props and runtime defaults beside it. It is now composed after the flat props, socssstill wins. Acssmap renders exactly as before. -
Type: the top-level
cssprop no longer accepts anumberor aComponentSelector, which never produced a style as the whole value; both are still valid inside one.boolean,nullandundefinedare still accepted, socss: active && { … }works. -
theme.*strings inside arrays (fontFamily: ['Arial', 'theme.font.body'], a stringcss, a string inside an arraycss) now resolve to their CSS variable on the client as they already did on the server, so both produce the same class. -
Theme functions with no
ThemeProviderare dropped wherever they sit, instead of reaching Emotion, which printed their source into the stylesheet and the server HTML (color:(t) =>t.system.primary;). In development a warning names the property, once.
-
-
#36
003c4a7Thanks @l7aromeo! - Stop the defaultflex-shrink: 0from overriding theflexshorthand (#33).A node that set
flex: '1 1 auto'hadflex-shrink: 0emitted after the shorthand, so it never shrank: a truncating label in a flex row pushed its siblings past the container edge instead of ellipsizing. Only numbers and thenone,autoandinitialkeywords were read; every multi-value shorthand fell through to the layout defaults.The shorthand is now parsed by the CSS grammar — one value (a number is the grow factor, anything else the basis),
<grow> <shrink>,<grow> <basis>,<grow> <shrink> <basis>, and the basis-first orders — and its shrink factor is used. A shorthand that cannot be read with certainty, such asvar(--flex)orinherit, gets no defaultflex-shrinkat all, so the browser applies it as written.Behaviour change:
flex: 0(number or string) now emitsflex-shrink: 1instead of0. CSS readsflex: 0as0 1 0%, so the old default overrode the author the same way; a zero slipped past because the check was a truthiness test. A node that relied onflex: 0not shrinking should setflex: '0 0 0%'orflexShrink: 0.flex: 1,auto,none,initial, an explicitflexShrink, and the defaults for flex containers and plain flex items emit exactly what they did before. -
#29
3856136Thanks @l7aromeo! - Name the missing prop instead of dying inside a layout effect.ThemeProvidercalled withoutmodesthrewCannot read properties of undefined (reading 'includes')from a minified helper during React's commit phase — and only for some readers. Adoption reads storage and the attribute, and with neither present the expression short-circuited before it touchedmodes, so a first-time visitor was fine while anyone who had ever chosen a theme got a dead page.tokens,modesanddefaultModeare required props, so TypeScript catches a missing one for a caller it sees. A sandbox, a JavaScript consumer, a CDN-cached bundle and a stale copy of a sample are callers it does not. Those are now told which prop is missing and what it is for, along with adefaultModeordefaultPreferencethat names a modemodesdoes not declare.The checks are not behind
setDebugModeor a development build: a caller that was never typechecked is most likely running a production bundle, so a check that goes quiet there protects nobody. This matches whatthemeScriptalready does with its own configuration. -
#36
53083d5Thanks @l7aromeo! - A render-prop function given as the only child of an HTML element renders its result.Div({ children: () => … })passed the function to React unchanged, which cannot render a function as a child: the browser rendered an empty element, and a server component failed to prerender. The same function inside an array,children: [() => …], already rendered its result, and now both do. A component still receives a function child unchanged, so it can call it itself. -
#36
af53324Thanks @l7aromeo! - Outside the React Server Components layer, function components withcssrender the same on the server as on the client.A function component given
css(one built withcreateNode, an Emotionstyledcomponent, a MUI component) had its styles compiled on the server into a class of its own, handed to it as a plainclassName, while the client renders it through Emotion. The two disagreed: the server's class had a different prefix, a component passing theclassNameon to a styled element kept two classes where the client merged them, and without Next'sStyleRegistrythe class's rule was never written. Hydration reported that the attributes did not match.In a plain
renderToStringorrenderToPipeableStreamapp, and in the server pass of a Next client component, these components now render through Emotion on the server too, so both sides produce the same classes, with or without an EmotionCacheProvider, and every class has its rule in the page. -
#36
f97eb1eThanks @l7aromeo! -StyleRegistryemits each Emotion rule once per page when Next's Cache Components are on, instead of twice (#35).With Cache Components, Next renders the client tree twice under one prerender, and each pass's registry flushed the whole page's rules. The flushed set is now shared by both passes of one render and kept apart between concurrent requests, and every rule the page needs is still emitted.
3.0.0 (2026-09-16)
Major Changes
-
#26
ddd859eThanks @l7aromeo! - OneThemeProvider, taking modes rather than a theme object.ThemeProvider({ modes: ['light', 'dark'], defaultMode: 'light', defaultPreference: 'system', // optional system: { light: 'light', dark: 'dark' }, // optional tokens, children, })ThemeModesProvideris removed — its behaviour is whatThemeProvidernow does. Thetheme: Themeshape is removed with it: a single-theme application declares one mode, and one concept is easier to hold than two that overlap.Migrating from 2.x.
ThemeProvider({ theme, children })becomesThemeProvider({ tokens: theme.system, modes: [theme.mode], defaultMode: theme.mode, children })for an application that has one palette, or a real mode list for one that has several. Token values should bevar(--…)references whose palettes live in CSS keyed by[data-theme="…"]; that is what makes a server-rendered document the same for every reader.setThemeis removed.setTheme(darkTheme)becomessetMode('dark').setTheme(previous => …)has no direct equivalent, because there is no theme object to derive a new one from — the mode is a name and the palette behind it is CSS. Anything that computed a theme from the previous one is now either a different mode name or a change to the token map itself.This also removes a trap rather than documenting it: under the mode-aware provider,
setThemecould only honour the mode a theme object named and discarded its palette silently, so a caller got a theme claiming one mode while emitting another's variables.One RSC fixture changes what it demonstrates rather than what it asserts. Its wrapper read storage, chose between two whole theme objects and handed one to the provider — the pattern this release removes — so it now picks a mode and the provider owns the rest. What the fixture exists to prove, MUI interop across a client boundary, is unchanged; the runtime theme swap it also happened to show is gone because an application no longer does that.
useTheme()returnstheme,mode,preference,modes,setMode,setPreferenceandhydrated.hydratedis false until the provider has adopted the reader's real mode: the first client render must match the server's, so anything rendering markup frommode— a toggle position, a different icon — should gate on it. Page-level theming does not need to: CSS keyed off[data-theme="…"]is correct from the first painted frame.
2.3.0 (2026-09-16)
Deprecated.
@meonode/uiversions before 3.0.0 are no longer maintained. Upgrade to the latest 3.x.
Minor Changes
-
#24
793c4fbThanks @l7aromeo! - Let a themed page be one document for every reader.A themed SSR page could not be cached, because the provider rendered its
:root{--meonode-theme-*}block from whichever theme it held and the application picked that theme per reader. Measured on a live site, light and dark differed in all 62 theme variables and the documents were different sizes.A second provider,
ThemeModesProvider, renders markup that does not depend on the mode:ThemeModesProvider({ tokens, // one map; values are var() refs modes: ['morning', 'night'], system: { light: 'morning', dark: 'night' }, // optional defaultMode: 'morning', children, })Token values are
var()references, the palettes live in CSS keyed by[data-theme="…"], and a blocking script sets that attribute before the first paint. The server then renders one document for everyone, with no flicker, because there was never a wrong first paint to correct.It takes one token map plus mode names rather than a theme per mode so the cacheable property holds by construction: with a single map the emitted block is the same whatever the reader stored, so the bytes cannot vary. A record of themes would let an application put the per-reader variation straight back.
useTheme()gainsmode,preference,setModeandsetPreferencebeside what it already returned. Onlymodeis meaningful underThemeProvider, where it reads the current theme's own mode;preferenceis absent there and the two setters throw, namingThemeModesProvider. Changing a mode while keeping the theme'ssystemwould not be a mode switch on that path — the palettes are different objects with different values, so it would emit one mode's variables under the other's name.preferenceis what the reader chose — possibly'system'— andmodeis what that resolves to; storing the resolved value instead would make following the OS impossible, since the first toggle would pin it.'system'is offered only when thesystemmapping is given, becauseprefers-color-schemeanswers in the OS's words and an application whose modes are'morning'and'night'has not said which is which until it says so.Two behaviours change on the new path only.
useThemeno longer writes the document: every consumer runs it, so a write there was a write per reader of the theme, which is how a provider on its default could overwrite a choice just made. And the mode is no longer compared against'dark'— the attribute is set to the mode name whatever it is, so a mode called'sepia'is no longer treated as light.ThemeProvider({ theme })keeps its existing behaviour exactly, including the class swapping, and is unaffected.ThemeModesProvideris the path to build on.ThemeProviderkeeps the shorter name only because taking it would break every existing application, not because it is the better default — a themed page it renders still varies per reader. The intended direction is that a future major makesThemeProvidermean this behaviour and the theme-swapping shape takes a legacy name or goes.Two components rather than two shapes on one, because
createNodeinfers a component's props and a union of the two collapses toneverthere, which stops every existing call site compiling. Keeping them separate also means a half-configured provider —tokenswithoutmodes— is a compile error rather than a throw at render. For the same reason there is no runtime rule about passing both: the situation cannot be expressed, since neither component accepts the other's props.ThemeProvideritself is untouched.The provider renders
defaultModeon its first pass — server and client alike — and adopts the reader's real mode in a layout effect, exposinghydratedon the context so a consumer can gate on the handover. Reading the attribute or storage while rendering makes the two first renders differ for every reader whose mode is not the default, which React requires to be identical. Alone that is invisible; combined with a consumer that renders anything frommode— a toggle's position, a different icon, a component present in only one mode — React throws #418, discards the server tree and client-renders the document. The readers who trigger it are exactly those who chose a non-default mode, which is rarely whoever is testing, sohydratedexists to let such a consumer wait for the handover deliberately.The page itself does not flash: page-level theming is CSS keyed off the attribute the pre-paint script already wrote. Only React markup that depends on the mode takes a second pass.
defaultPreferencesays what a reader who has never chosen starts on, which the mode cannot express:defaultMode: 'night'means "dark when nothing is stored", whiledefaultPreference: 'system'means "follow the OS until told otherwise". It defaults todefaultMode, a stored choice outranks it, and'system'without asystemmapping throws at construction — unlike a stored'system'with no mapping, which is a reader's leftover and degrades quietly. The field is also what lets one configuration literal feed both the pre-paint script and the provider, which is not possible while only one half declares it.A site that declares its mode names through
MeoThemegets them everywhere. The provider's props, the mapping insystem, and everythinguseTheme()hands back —mode,modes,setMode,setPreference— takeResolvedThemeMode, which isMeoTheme['mode']when augmented and the looseThemeModewhen not, so an un-augmented site is unaffected. Augmented,setMode('nigth')stops compiling; before, every string was accepted and autocomplete offered'light'and'dark'to a site that uses neither. A misspelt mode is consistent with itself, so no runtime check can catch it —defaultModeis a member ofmodes, every validation passes, and no stylesheet matches.Rejections are explicit rather than silent. A mode that is not in
modes, and'system'without the mapping, are both ignored with a development warning instead of setting adata-themeno selector matches. Passingthemealongsidetokenstakes the mode path and says so once. A stored value that is not one of the declared modes is discarded fordefaultMode, which is also what makes an upgrade safe: the original path writeslocalStorage.themeas a mode and this one stores a preference under the same default key, so a leftover value is honoured when it is still a declared mode and dropped otherwise.ThemeModewas'light' | 'dark' | string, which collapses tostringand lost even those two from autocomplete. It is now'light' | 'dark' | (string & {}), which keeps them as hints without closing the set.MeoThemeaugmentation ofmodestill overrides it. -
#24
8ad3e5eThanks @l7aromeo! - Apply the theme before the first paint, from a script the server can cache.A document that names the reader's mode in its markup cannot be shared: either every reader gets their own render, or somebody gets the wrong one and watches it correct itself.
themeScript(config)removes the reason to do either. It returns a plain inline<script>node for<head>that reads the stored preference and stamps the resolved mode on the document element, in the window after that element exists and before the first paint. Palettes live in CSS keyed by[data-theme="…"], so the server sends one document to everybody and there is no flicker, because there was never a wrong first paint to correct.const theme = { modes: ['morning', 'night'], defaultMode: 'morning', system: { light: 'morning', dark: 'night' }, // optional defaultPreference: 'system', // optional; defaults to defaultMode } as const Html({ suppressHydrationWarning: true, children: [ Head({ children: [themeScript(theme), Link({ rel: 'stylesheet', href: '/app.css' })] }), Body({ children: ThemeModesProvider({ ...theme, tokens, children }) }), ], })The field names are the provider's, so one object feeds both and the two cannot drift.
The configuration travels in an attribute; the body is a constant. The element is
<script data-meonode-theme='{…}'>with a fixed body that reads that attribute back and parses it. Nothing an application names is ever interpolated into JavaScript, so a mode calleda</script>…has no context to escape from — the injection class is absent rather than defended against. It also means the body is the same bytes for every application and every configuration, so under a hash-only CSP itsscript-src 'sha256-…'is one value that never changes, rather than one per application that moves whenever somebody renames a mode.THEME_SCRIPT_CSP_HASHis exported alongside it, so a policy can name the script without rendering anything first:headers.set('Content-Security-Policy', `script-src 'self' '${THEME_SCRIPT_CSP_HASH}'`)Take it from the package rather than copying the digest out of rendered output. A copied literal goes stale the first time this body changes, and that failure is silent in every way a test usually looks. The policy is still valid, the element is still in the document, and the page still settles in the right mode, because the provider applies it after hydration. The one symptom is that
data-themeis absent until hydration — which is precisely the flash the script exists to prevent, and nothing downstream of hydration can observe it.Three assertions make that visible, and none of them costs anything in the script:
- the served
script-srccontainsTHEME_SCRIPT_CSP_HASH. This catches a stale literal exactly, before a browser is involved. - no
csp-violationreport arrives. Measured against a deliberately stale hash, aReportingObserverforcsp-violationwithbuffered: truereports it witheffectiveDirective: 'script-src-elem'even when it is registered after the page has loaded, which a test usually is. Asecuritypolicyviolationlistener has to be in place before the parser reaches the script, so it is the wrong shape for this. data-themeis present before hydration — read it atload, not after. After hydration the provider has written it and the check can no longer fail.
A policy whose hashes are derived from the rendered response, as a hashing proxy does, is unaffected: it re-derives the digest per response and never holds a copy to go stale.
defaultPreferenceis where a reader starts;defaultModeis where everything lands when it fails. They are usually the same and do not have to be, and only the first can be'system'. A first visit is the one case where the OS preference is all that is known about what the reader wants, anddefaultModecannot express following it: it has to name a mode, and a mode may not be calledsystem. So a site that wants to begin by asking the OS saysdefaultPreference: 'system', and still names adefaultModefor when nothing can answer — a blocked storage, a missingmatchMedia, a stored mode that has since been renamed. Omitted, it isdefaultMode, so nothing existing moves.A site that declares its modes gets them checked.
modes,defaultMode,defaultPreferenceand both sides ofsystemare typed asResolvedThemeMode, which isMeoTheme['mode']when a site augments it and the looseThemeModewhen it does not — so an un-augmented site sees no change at all, and an augmented one gets autocomplete for its own names and a compile error on a misspelt one. That case is otherwise invisible: a typo inmodesis consistent with itself, sodefaultModeis still a member ofmodes, every runtime check passes, and the only symptom is a stylesheet that matches nothing.declare module '@meonode/ui' { interface MeoTheme { mode: 'morning' | 'night' } }Three things an application has to do, and the reasons they are not optional:
- Put the script as early in
<head>as the framework allows. A classic inline script that follows a<link rel="stylesheet">cannot execute until that sheet has loaded. Measured in the Next app router, first is not achievable: React hoists adata-precedencestylesheet above anything a layout renders. That costs latency rather than correctness — the sheet ahead of it is render-blocking too, so nothing is painted before the script runs — but a slow or third-party stylesheet ahead of it delays the mode for no reason. - Define a usable palette at
:root, with[data-theme="…"]blocks as overrides. Every failure path — blocked storage, a sandboxed frame, a missingmatchMedia— ends with nodata-themewritten. Palettes that exist only under the attribute leave those readers with an unstyled page, which is a worse outcome than the wrong mode.
The pre-paint claim is asserted as far as it can be: a probe placed immediately after the script in
<head>reports the attribute already set, which bounds the write to before the body is parsed. That is a one-sided bound and not a comparison against first paint, which the Paint Timing API did not make available.- Set
suppressHydrationWarningon the element the script writes to. The attribute is not in the server markup, which is the definition of a mismatch.
Failures are contained where containing them helps.
localStoragethrows on property access, not merely fromgetItem, where site data is blocked and in a sandboxed iframe withoutallow-same-origin; that read is guarded on its own so those readers still get the default rather than nothing.matchMediais guarded separately too, falling back to the light name, which is what the provider falls back to as well. A storedsystemwith no mapping resolves to the default instead of stamping the wordsystem, which no palette matches.A configuration that cannot work throws. An empty
modes, adefaultModeor asystemvalue that is not one of them, adefaultPreferencethat is neither a mode nor'system', adefaultPreference: 'system'with no mapping to say what the OS's words mean here, a mode namedsystem, or a name outside[A-Za-z0-9_-]{1,64}. Not gated on development: the configuration is authored rather than data, so a check that fired only in development would let CI pass and production ship a script that stamps a mode no selector matches. The name restriction is for the application's sake — a mode name is the one value that crosses from this configuration into[data-theme="…"]selectors written by hand. - the served
Patch Changes
-
#24
78fef6dThanks @l7aromeo! - Say which shape produced a missing-key report.The call-site line named where a generated list was written. When that list has been spread in beside children written out by hand, the child React names is usually one of those siblings — a heading has no key because nobody writes keys on headings — while the rows, which do have keys, look like the problem. The reader audits the rows.
When a marked list holds both keyed and unkeyed children, the line now says so and names the fix, which is not the same fix as the all-unkeyed case:
[MeoNode] A generated list at app/page.tsx:41:7 has children without a `key`. React reports the missing key itself; this names the call site it came from. Some children here do have keys: a spread puts a generated list and the siblings written beside it into one list, so React asks those siblings for keys too. Nest the generated part instead of spreading it.The explanation no longer waits for a line number. The call site needs the plugin's
callSiteLocationsoption, which most builds do not set; the shape needs only the list marker, which every compiled build emits. So a build without locations now gets the clause, plus a line saying which option would name the file:[MeoNode] A generated list has children without a `key`. Some children here do have keys: a spread puts a generated list and the siblings written beside it into one list, so React asks those siblings for keys too. Nest the generated part instead of spreading it. Turn on `callSiteLocations` in the @meonode/compiler plugin options to have this name the file and line.Without a location, only the mixed case speaks. React already reports an all-unkeyed list correctly, and repeating that with no line number is noise.
A list where nothing is keyed is left alone — there the fix is keys, and blaming the spread would send the reader the wrong way.
2.2.1 (2026-09-14)
Deprecated.
@meonode/uiversions before 3.0.0 are no longer maintained. Upgrade to the latest 3.x.
Patch Changes
-
#21
9425c1bThanks @l7aromeo! - Typechildrenas the nesting the runtime already accepts.2.2.0 let a
childrenarray contain arrays, andChildrenwas never widened with it:export type Children = NodeElement | NodeElement[]So the types permitted exactly the shape the runtime change exists to replace — a spread — and rejected the one it enables:
error TS2322: Type '(NodeInstance<"span"> | NodeInstance<"span">[])[]' is not assignable to type 'NodeElement<NodeElementType, Record<string, unknown>>[]'It is now recursive, and its arrays are readonly so that an
as constlist is a valid children list:export type Children = NodeElement | readonly Children[]readonlycosts existing callers nothing — a mutable array is assignable to a readonly one, and the patterns that read an array back out of aChildrenvalue go throughArray.isArray, which narrows toany[]either way. Checked against the built.d.tsfrom a consumer project: mutating after a guard, returning one asNodeElement[], spreading one, and passing aNodeElement[]as children all compile before and after; only the nested form changes answer.The accompanying
.test-d.tsis the part that was missing. Nesting was mutation tested, RSC tested and measured for allocation, and still shipped unusable from TypeScript, because every one of those tests exercises the feature from JavaScript — where the annotation has no say.
2.2.0 (2026-09-14)
Deprecated.
@meonode/uiversions before 3.0.0 are no longer maintained. Upgrade to the latest 3.x.
Minor Changes
-
#19
04959dfThanks @l7aromeo! - Say where an unkeyed list came from.React cannot answer that in a MeoNode app.
Div({...})only builds a node;createElementfires later inside.render(), so React attributes every element in the tree to that one call — every missing key anywhere in a file reports at the same line, and the owner stack collapses to the same component for the same reason.The compiler already computed the call site and discarded it. With the new
callSiteLocationsoption it emits the source position, and the runtime names it beside React's own report rather than in place of it:[MeoNode] A generated list at src/app/page.ts:124:6 has children without a `key`.It prints only when a child actually lacks a key, so a correctly keyed list stays silent. It also prints in the one case React says nothing at all: a marked list whose children reach a host element through an unmarked wrapper, where the variadic hand-off tells React a human wrote the siblings out.
Opt in through the plugin options. The locations are absent from any build that does not ask for them; with them on, the whole of a 55-file app grew by 759 bytes gzipped.
-
#19
04959dfThanks @l7aromeo! - Let achildrenarray contain arrays.Div({ children: [Span('a'), items.map(render)] })used to throwObjects are not valid as a React child, naming MeoNode's internal fields. The only way to combine siblings you wrote out with a generated list was to spread them into one array — and a spread is the one shape that costs those siblings React's missing-key exemption, because React grants it only while the generated part stays a nested array.So the shape that read most clearly was the shape the library then reported on. Nesting now works at any depth, and behaves as React does:
// the heading is exempt; the list is still key-checked Div({ children: [Span('heading'), rows.map(r => Row({ key: r.id, ...r }))] })A children array that contains itself is dropped at the self-reference with a development warning rather than overflowing the stack, and a function child inside a nested array is normalised like any other — it previously went missing from the output with nothing said.
Flat arrays, which are nearly all of them, skip the new traversal entirely: measured against the previous release, zero allocation difference on the flat, single-child and props-only shapes.
Patch Changes
-
#19
04959dfThanks @l7aromeo! - Report a throwing function-as-a-child, and stop paying to ask whether to.A function child that threw had its error swallowed and its result replaced with
null, so the element vanished from the page with nothing printed unlesssetDebugModehappened to be on — and nobody discoverssetDebugModefrom a blank space. The error belongs to the caller, so it now appears in development like any other diagnostic. MeoNode's own recovery paths stay behindsetDebugMode: a compiled marker bucket collision, a failed prototype probe and a key-name fallback are the library recovering from itself, and nobody outside it can act on them.Separately,
diagnosticsEnabled()readprocess.env.NODE_ENVon every call, which in Node is a nativegetenvrather than a property read — roughly 112ns against 0.9ns for a boolean. It is consulted at every recursion level of the theme diagnostics, once per styled node, on every render, while the function's own documentation claimed production cost a single boolean check. The environment is now read once.setDebugModestill takes effect at runtime. -
#19
04959dfThanks @l7aromeo! - Catch a node passed as a DOM attribute on a plain HTML tag.Div({ title: Span('x') })renderedtitle="[object Object]"and said nothing. It now throws, naming the prop and the call site.Host tags only. Passing a node in a prop to a component stays supported — the receiver may put it into its own children, where it resolves correctly, and only the receiver knows which was meant. A plain tag has no receiver, so the case is decidable and always wrong.
2.1.0 (2026-09-13)
Deprecated.
@meonode/uiversions before 3.0.0 are no longer maintained. Upgrade to the latest 3.x.
Minor Changes
-
#16
4d42425Thanks @l7aromeo! - Report an unkeyed list, the way React does.Expect new warnings on code that compiles clean today. Any keyless
items.map(fn)list starts reporting React's own missing-key warning once it is built with a compiler that emits the marker, and run on a@meonode/uithat understands it — this release or later. That is the feature working — those lists reconcile by position, so a delete or a reorder already hands a row the previous row's state — but it is a behaviour change, not a silent fix. The warning itself is development-only. The decision behind it is not free in production, but it is close: one property read and one falsy test per compiled node, short-circuiting before anything else on a call site whose children were not generated. Adding akeyto each row resolves it, and is the fix for the underlying bug too.For, added in this same release, is the ergonomic way to do that: it takes the data rather than the finished rows and keys each one from it, so no key has to be threaded through every row by hand. It is a plain runtime helper and needs no compiler — which matters here, because this warning only appears for builds that use@meonode/compiler, while the positional-reconciliation bug it reports is there either way. If you are not compiling, you will not see the warning andForstill fixes the problem.Both halves are required, and an old pairing is worse than no pairing. A compiler that emits
__meo$listrunning against an earlier@meonode/uiproduces no warnings at all — it produces console noise instead. No earlier runtime strips the key, so it reaches the element, and React rejects__meo$listas an attribute name and says so once per render of every marked call site, on the same console channel the key warnings would have used. Rendering is unaffected and production is untouched, but the diagnostic is not merely absent, it is replaced by something louder and less useful.Test for the capability rather than a version, since it is the capability that decides: a runtime understands the key when its
COMPILER_SCHEMA_KEYSentries for schemas 2 and 3 carry alistfield. That stays true however the version numbers land. This is a compatibility requirement of the marker contract, not a bug in either half — upgrade both together.An unkeyed list reconciles by position, so deleting or reordering a row hands the next one the row before it — its state, its focus, whatever the user had typed into it. React reports that; MeoNode did not, for any list, because
render()spreads children variadically:createElement(div, null, a, b) // 0 reports createElement(div, null, [a, b]) // 1 report, same childrenSeparate arguments are React's signal that a human wrote the siblings out, so it marks them validated and never asks for keys. Every MeoNode list took that form, so every list was silently exempt from the check — including the ones that most needed it.
The spread is not going away: it is what keeps authored siblings quiet, which is a promise the library makes deliberately. Only children the compiler has marked as generated — a
.map(), a spread, an IIFE, a helper call — are now handed to React as one array, and React raises its own warning from there. No message is reimplemented here.That distinction has to come from the compiler, because it cannot be recovered at runtime: arguments are evaluated before the callee runs, so
items.map(fn)has already collapsed into an ordinary array by the timeDiv({ children })is entered, indistinguishable from one typed out by hand. Inferring list-ness from the value would nag in exactly the places React stays quiet.@meonode/compilersets__meo$liston a generated call site; this release is the half that reads it, on marker schemas 2 and 3.A generated list is reported however few rows it holds — a
.map()over one row is exactly the list that grows to three later, carrying the bug with it. The marker says the expression was generated; the value it produced still decides, sochildren: row, a variable holding a single node that the compiler cannot see inside, stays silent. React draws the line in the same place: an unkeyed one-element array is reported, a bare child is not.Nothing about reconciliation changes. Spread and array reconcile identically — both wrong unkeyed, both correct keyed — so this restores a diagnostic and nothing else. Uncompiled code, and code compiled by a version that does not set the marker, behaves exactly as before.
-
#16
d377a51Thanks @l7aromeo! - AddFor, a list helper that keys rows from the data.React matches unkeyed children by position, so deleting or reordering a list hands each surviving row the previous row's fiber — its state, its focus, whatever the user had typed into it.
children: items.map(...)inherits that in full, and nothing in the library can fix it after the fact: by the timeDiv({ children: ... })is called,.map()has already run and its result is an ordinary array, indistinguishable at runtime from one written out by hand. The compiler can still tell them apart, because it sees the source — that is what the list diagnostic in this release is built on. But knowing a list was generated is not the same as knowing which row is which, so it can report the problem and only the data can fix it.Fortakes the data instead of the finished nodes, so the identity is still there to use:Div({ children: For(todos, todo => TodoRow({ todo })) })The callback receives
(item, index, key), matchingArray.maprather than inverting it — the key is the oneForapplied, there for a row that wants to reuse it and ignorable otherwise.Rows are identified by object reference, which survives their contents changing — renaming a row keeps its state, where hashing the contents would discard it on every keystroke. Primitives identify themselves by value, and two genuinely identical items fall back to their position, which is no worse than the unkeyed list they replace.
Reference identity cannot follow items that are rebuilt between renders, so
For(items, renderItem, item => item.id)takes an explicit accessor. In development, a list whose items are all new on a second render is reported once, with that suggestion — on the first render every object is new, which says nothing, so the check waits for a repeat before concluding anything.Unkeyed
children: items.map(...)still reconciles exactly as it does in React — by position, so a delete or a reorder hands a row the previous row's state. What changed in this release is that you now hear about it: see the list-diagnostic entry above.Foris the ergonomic answer to that warning, and unlike the warning it needs no compiler.
Patch Changes
-
#16
5769190Thanks @l7aromeo! - Rebuild a memoized subtree when the node behind it is a different node.Every node given
depsrenders as aMeoMemoelement, so two structurally different subtrees in the same position presented React with the same component type. React kept the fiber where it would otherwise have remounted, anduseMemo— seeingdepscompare equal — returned the element built for the previous node:Div({ children: cond ? Div({ children: 'A' }, []) : Section({ children: 'B' }, []) }) // after cond flips: still <div>A</div>A key did not rescue it either. Giving the slot one stable key, which is the natural thing to write, still rendered the stale branch; only differing keys worked, which is the opposite of how keys behave everywhere else.
node.elementnow joins the dependency list. The element type is stable for a given call site, so an ordinary re-render still hits the memo, while a genuine swap invalidates it.This restores React's own rule rather than inventing one. Measured against plain React with
useMemo([])in both directions: a swap between two different component types remounts and rebuilds, and a swap between two of the same type keeps the frozen value.[]continues to mean freeze, exactly asuseMemo([])does.This is a separate mechanism from the list diagnostic in this release, and does not change it: unkeyed lists still reorder and delete exactly as they do in React, and still need a
key.
2.0.2 (2026-08-28)
Deprecated.
@meonode/uiversions before 3.0.0 are no longer maintained. Upgrade to the latest 3.x.
Patch Changes
-
#14
aeb8791Thanks @l7aromeo! - Scope server-rendered styles to the request that produced them.The Emotion cache a server render collects into was reached through
getGlobalState, a plain module-level object, so one cache served every request in the process andcache.insertedaccumulated every style ever rendered.StyleRegistryflushes that cache into the response, and its per-request dedupe set cannot tell "this request's styles" from "some earlier request's", so each page shipped the union of every route the process had touched.Measured on the documentation site:
/docs/hooksneeds 32 KB of CSS and was serving 166 KB — 5.2x — with 189 of its 237 declared classes belonging to other pages. It grows towards the union of the whole site as more routes are hit, and it lands on a document that isno-storebecause of the CSP nonce, so nothing can cache it away.StyleRegistrynow opens a scope per render and the compiler writes into that. Same page, same server: 166 KB -> 32 KB, and stable across repeat requests instead of growing.A scope adopts any rules compiled before it opened.
StyleRegistryis a client component, so it renders in the SSR pass, after the server components above it have already compiled theircss— scoping without that step stranded their rules, putting the class in the markup while its declaration never reached the document. The existing coverage for a themedLinkon a server page caught it.Three tests were added around the concurrency case, since the scope is held in a module-level binding: a route must emit the same styles whether it renders alone or alongside others, two routes rendering together must not absorb each other's, and one route fetched twice with traffic in between must not grow.
Client rendering is untouched;
bun run benchis unchanged.
2.0.1 (2026-08-28)
Deprecated.
@meonode/uiversions before 3.0.0 are no longer maintained. Upgrade to the latest 3.x.
Patch Changes
-
#12
09d74baThanks @l7aromeo! - Remove four internal members left behind when the element cache was replaced.NodeUtil.hashString,NodeUtil.hashCSS,NodeUtil.isStylePropandNodeUtil.shouldNodeUpdateall existed to serve the derived-key element cache — hashing props into a lookup string, and deciding whether a node's dependency list allowed it to be skipped. Nothing computes a key or a signature any more, so all four lost their last caller and shipped inert.Confirmed dead before removing: each appears exactly once across
src,tests,scriptsandbench— its own definition — and zero times in@meonode/mui,@meonode/compileror the documentation site.NodeUtilis not exported from any package entry (.,./client,./nextjs-registry), so none of them were reachable from outside the package and no consumer can be relying on them.No behaviour change. All suites pass unchanged: 270 unit, 271 compiled, 76 RSC in each mode.
2.0.0 (2026-08-27)
Deprecated.
@meonode/uiversions before 3.0.0 are no longer maintained. Upgrade to the latest 3.x.
Major Changes
-
#10
7482625Thanks @l7aromeo! - Replace the element cache with fiber-backed memoization.A node given
depshad its rendered element held in one process-globalMap, keyed by a string derived from the node's props and its position under the render root. Deriving identity that way carried two structural bugs:- Collisions. Two different subtrees that derived the same string shared one
entry, and the second rendered the first's content. React composes what a
component returns, so the positional chain restarted at every component
boundary — which made two components with structurally identical trees, and
two instances of one component, both collide.
@meonode/compilernarrowed this with__meo$k, a source-position hash, but could not close it: two instances of the same component share a source position. - No release. Only a render root carried an unmount hook, so a memoized child was never evicted when it unmounted. Twenty memoized rows, list emptied, whole tree unmounted, left twenty entries — cleared only by the next SPA navigation.
A memoized subtree now renders inside a
MeoMemofiber holdinguseMemo(() => node.render(), deps). Identity is the fiber, so there is nothing to derive and nothing to collide, for plain function components andComponentalike. Release is React dropping the fiber.depssemantics are unchanged.Faster, not slower, because the derived key is what cost the most:
before after node construction 31.89 ms 13.24 ms (2.4x) client render 23.73 ms 17.07 ms (1.39x) 200 memoized rows x 31 renders 14.85 ms 8.74 ms (1.70x) entries left after unmount 200 0 depsnow means literally what React means by it. Previously the cache key folded in a signature of the node's props, so a prop change invalidated the entry regardless of the dependency list —deps: []did not really mean "never rebuild", it meant "rebuild whenever a prop changes". The list is now handed straight touseMemo, sodeps: []freezes the subtree and a node that should follow a value has to declare it:// 1.x rebuilt this when `id` changed. It no longer does. Div({ ...props, padding: '4px' }, []) // Declare what it follows. Div({ ...props, padding: '4px' }, [props.id])Server rendering is untouched: nothing was ever memoized there, and
MeoMemois client-only and rendered rather than called, so it never crosses an RSC boundary.Breaking. Everything the derived key needed is gone:
BaseNode.elementCache,BaseNode.cacheCleanupRegistry,BaseNode.clearCachesandNode.clearCachesNodeInstance.signatureand the deprecatedNodeInstance.stableKeyNodeUtil.createPropSignature,NodeUtil.hashDynamicValues,NodeUtil.extractCriticalProps,NodeUtil.shouldCacheElementMountTrackerUtilandNavigationCacheManagerUtil, including itshistory.pushState/replaceStatepatchingrender()'sparentBlockedandscopeparameters.render(container, node)from@meonode/ui/clientno longer needs a per-container namespace and takes the same arguments as before.
Most applications call none of these. Code that called
Node.clearCaches()between tests or on navigation can simply drop the call.@meonode/compileroutput stays compatible.__meo$kand__meo$dynare accepted and stripped, just no longer read, so the plugin remains a pure build-time speedup — a smaller one, because uncompiled construction got much faster. - Collisions. Two different subtrees that derived the same string shared one
entry, and the second rendered the first's content. React composes what a
component returns, so the positional chain restarted at every component
boundary — which made two components with structurally identical trees, and
two instances of one component, both collide.
Patch Changes
-
#9
20936eaThanks @l7aromeo! - Publish the prerelease line from CI, and preflight releases properly.CI ran only on
main, and the Release workflow only fired on a successful CI run formain, so thebetaline had no sanctioned publish path at all — leaving a laptop as the only option.@meonode/uiand@meonode/compilersetpublishConfig.provenance, which npm can only satisfy from a provider it recognises, so a localchangeset publishshipped@meonode/mui(no provenance) and then failed on the other two. That left a released package whose peer dependency did not exist on the registry.Both workflows now trigger on
betaas well, so the prerelease line publishes the same waymaindoes, with attestations intact. The trigger has to be listed on the default branch to take effect at all — GitHub reads aworkflow_runworkflow from there, so editing it onbetaalone changes nothing.Added
bun run release:dry, run in the release job before anything is published.npm publish --dry-runalone would not have caught this: it packs the tarball and reports success without ever evaluatingpublishConfig.provenance. The preflight checks what actually decides the outcome — that every package asking for provenance is somewhere it can be minted (on GitHub Actions, that meansid-token: write, not merely "in CI"), and that no version is already on the registry — then defers tonpm publish --dry-runfor packing,filesand auth.
2.0.0-beta.0 (2026-08-27)
Deprecated.
@meonode/uiversions before 3.0.0 are no longer maintained. Upgrade to the latest 3.x.
Major Changes
-
7482625Thanks @l7aromeo! - Replace the element cache with fiber-backed memoization.A node given
depshad its rendered element held in one process-globalMap, keyed by a string derived from the node's props and its position under the render root. Deriving identity that way carried two structural bugs:- Collisions. Two different subtrees that derived the same string shared one
entry, and the second rendered the first's content. React composes what a
component returns, so the positional chain restarted at every component
boundary — which made two components with structurally identical trees, and
two instances of one component, both collide.
@meonode/compilernarrowed this with__meo$k, a source-position hash, but could not close it: two instances of the same component share a source position. - No release. Only a render root carried an unmount hook, so a memoized child was never evicted when it unmounted. Twenty memoized rows, list emptied, whole tree unmounted, left twenty entries — cleared only by the next SPA navigation.
A memoized subtree now renders inside a
MeoMemofiber holdinguseMemo(() => node.render(), deps). Identity is the fiber, so there is nothing to derive and nothing to collide, for plain function components andComponentalike. Release is React dropping the fiber.depssemantics are unchanged.Faster, not slower, because the derived key is what cost the most:
before after node construction 31.89 ms 13.24 ms (2.4x) client render 23.73 ms 17.07 ms (1.39x) 200 memoized rows x 31 renders 14.85 ms 8.74 ms (1.70x) entries left after unmount 200 0 depsnow means literally what React means by it. Previously the cache key folded in a signature of the node's props, so a prop change invalidated the entry regardless of the dependency list —deps: []did not really mean "never rebuild", it meant "rebuild whenever a prop changes". The list is now handed straight touseMemo, sodeps: []freezes the subtree and a node that should follow a value has to declare it:// 1.x rebuilt this when `id` changed. It no longer does. Div({ ...props, padding: '4px' }, []) // Declare what it follows. Div({ ...props, padding: '4px' }, [props.id])Server rendering is untouched: nothing was ever memoized there, and
MeoMemois client-only and rendered rather than called, so it never crosses an RSC boundary.Breaking. Everything the derived key needed is gone:
BaseNode.elementCache,BaseNode.cacheCleanupRegistry,BaseNode.clearCachesandNode.clearCachesNodeInstance.signatureand the deprecatedNodeInstance.stableKeyNodeUtil.createPropSignature,NodeUtil.hashDynamicValues,NodeUtil.extractCriticalProps,NodeUtil.shouldCacheElementMountTrackerUtilandNavigationCacheManagerUtil, including itshistory.pushState/replaceStatepatchingrender()'sparentBlockedandscopeparameters.render(container, node)from@meonode/ui/clientno longer needs a per-container namespace and takes the same arguments as before.
Most applications call none of these. Code that called
Node.clearCaches()between tests or on navigation can simply drop the call.@meonode/compileroutput stays compatible.__meo$kand__meo$dynare accepted and stripped, just no longer read, so the plugin remains a pure build-time speedup — a smaller one, because uncompiled construction got much faster. - Collisions. Two different subtrees that derived the same string shared one
entry, and the second rendered the first's content. React composes what a
component returns, so the positional chain restarted at every component
boundary — which made two components with structurally identical trees, and
two instances of one component, both collide.
1.8.7 (2026-08-02)
Deprecated.
@meonode/uiversions before 3.0.0 are no longer maintained. Upgrade to the latest 3.x.
Patch Changes
- #1
5d234acThanks @l7aromeo! - Point the package metadata at the new monorepo.repository,bugsandhomepagereferenced the three separate repositories that have now been merged into l7aromeo/meonode, and each package gains arepository.directoryso npm links to its own subtree. No functional change — npm reads these fields from the published tarball, so a release is the only way to correct them.
v1.8.6 (2026-08-01)
Bug Fixes
- theme: give numeric theme tokens their unit on length properties (c1708d1)
- theme: warn on tokens and values that silently produce no style (e487ac0)
- tsconfig: typecheck the scripts directory (dc05192)
v1.8.6-beta.1 (2026-08-01)
Bug Fixes
- theme: give numeric theme tokens their unit on length properties (c1708d1)
- theme: warn on tokens and values that silently produce no style (e487ac0)
- tsconfig: typecheck the scripts directory (dc05192)
v1.8.5 (2026-08-01)
Bug Fixes
v1.8.4 (2026-08-01)
Bug Fixes
v1.8.3 (2026-08-01)
Bug Fixes
v1.8.2 (2026-08-01)
Bug Fixes
v1.8.1 (2026-08-01)
Bug Fixes
v1.8.0 (2026-08-01)
Features
v1.7.8 (2026-08-01)
Bug Fixes
v1.7.7 (2026-08-01)
Performance Improvements
v1.7.6 (2026-07-31)
Bug Fixes
v1.7.5 (2026-07-31)
Bug Fixes
v1.7.4 (2026-07-29)
Bug Fixes
v1.7.3 (2026-07-29)
Performance Improvements
v1.7.2 (2026-07-28)
Performance Improvements
v1.7.1 (2026-07-27)
Performance Improvements
v1.7.0 (2026-07-27)
Bug Fixes
- compiler: classify runtime-merged passthrough props in fast path (6b88a8a)
- theme: emit theme CSS variables from ThemeProvider itself (92c3b43)
Features
- compiler: add compiled marker constants and types (b0e838b)
- compiler: add schema 2 marker contract with namespaced bucket keys (48a5fa7)
- compiler: call-site stable key fast path (cfed30a)
- compiler: css property list JSON export for compiler codegen (9fb44a2)
- compiler: processProps fast path for compiled marker props (24f6000)
v1.7.0-beta.2 (2026-07-27)
Features
- compiler: add schema 2 marker contract with namespaced bucket keys (48a5fa7)
v1.7.0-beta.1 (2026-07-26)
Bug Fixes
- compiler: classify runtime-merged passthrough props in fast path (6b88a8a)
Features
- compiler: add compiled marker constants and types (b0e838b)
- compiler: call-site stable key fast path (cfed30a)
- compiler: css property list JSON export for compiler codegen (9fb44a2)
- compiler: processProps fast path for compiled marker props (24f6000)
v1.6.2 (2026-07-02)
Bug Fixes
- deps: update dev dependencies to latest versions (8608467)
v1.6.1 (2026-06-26)
Bug Fixes
- types: allow nested css literals without as const (23942b1)
v1.6.0 (2026-06-25)
Features
- core: add Emotion-style
aselement polymorphism (7dbd75d)
v1.5.3 (2026-06-25)
Bug Fixes
- deps: update dependencies and align peer declarations (d17b2a4)
v1.5.2 (2026-05-10)
Bug Fixes
- add missing WebkitAppRegion to CSS property constants (02028b0)
v1.5.1 (2026-04-26)
Bug Fixes
- types: harden DeepPaths for augmented MeoTheme (b5ca3a9)
v1.5.0 (2026-04-26)
Features
- types: add MeoTheme augmentation for typed theme tokens (36bce42)
v1.4.6 (2026-04-25)
Bug Fixes
- theme: resolve at-rule keys to concrete values on server (a9d9dc0)
Performance Improvements
- theme: memoize replaceThemeTokensWithCssVars via module WeakMap (4afe0bf)
v1.4.5 (2026-04-25)
Bug Fixes
- theme: resolve theme tokens carried by non-css props (sx, style) (eccb53e)
- types: point package exports to the dist/types output (98a5540)
v1.4.4 (2026-04-24)
Bug Fixes
- emotion: align server theme token resolution with client vars mode (9e9f9c4)
- emotion: prevent empty-css hydration class drift (45edd78)
- rsc: emit theme tokens as CSS vars in server style output (7a07cc1)
- rsc: keep client-reference styling on a single render path (25b8580)
- rsc: stop re-wrapping client-reference elements during child normalization (ebee585)
- theme: keep client render output in CSS var mode (e95316b)
v1.4.4-beta.6 (2026-04-24)
Bug Fixes
- emotion: align server theme token resolution with client vars mode (9e9f9c4)
v1.4.4-beta.5 (2026-04-24)
Bug Fixes
- emotion: prevent empty-css hydration class drift (45edd78)
v1.4.4-beta.4 (2026-04-24)
Bug Fixes
- rsc: stop re-wrapping client-reference elements during child normalization (ebee585)
v1.4.4-beta.3 (2026-04-24)
Bug Fixes
- rsc: keep client-reference styling on a single render path (25b8580)
v1.4.4-beta.2 (2026-04-24)
Bug Fixes
- theme: keep client render output in CSS var mode (e95316b)
v1.4.4-beta.1 (2026-04-24)
Bug Fixes
- rsc: emit theme tokens as CSS vars in server style output (7a07cc1)
v1.4.3 (2026-04-23)
Bug Fixes
- exports: keep StyleRegistry out of core main entrypoint (58b3eb3)
v1.4.2 (2026-04-23)
Bug Fixes
- rsc: resolve themed props in server-bypassed client reference path (551e11c)
v1.4.1 (2026-04-23)
Bug Fixes
- node: avoid server crashes when reading client-reference marker flags (4bb2aaf)
v1.4.0 (2026-04-23)
Bug Fixes
- rsc: stabilize server-side emotion output for themed client references (5c97bbd)
Features
- registry: expose shared StyleRegistry entrypoint (f7377a2)
v1.3.0 (2026-04-22)
Bug Fixes
- ci: load rollup TypeScript config reliably in workflows (6a81783)
Features
- node: support async node-like values in children resolution (97becc7)
v1.3.0-beta.1 (2026-04-21)
Bug Fixes
- ci: load rollup TypeScript config reliably in workflows (6a81783)
Features
- node: support async node-like values in children resolution (97becc7)
v1.2.0 (2026-02-15)
MeoNode UI v1.2.0 – Stack-Based Portal System
Release Date: 2026-02-15
Features & Refactors
- portal: Implemented a new stack-based architecture using
PortalProviderandPortalHostfor managed overlay stacks (7196f6f). - helper: Introduced
createDataChannelfor high-performance, subscription-based state updates (6508c0a). - hook: Added
usePortalanduseDataChannelhooks for reactive interaction with the new systems (b2f7d19). - core: Refactored
BaseNodeandNodeUtilfor better portal integration and removed legacy logic (91ced63).- BREAKING CHANGE: Legacy
portal.hoc.tshas been removed in favor of the new Provider architecture.
- BREAKING CHANGE: Legacy
Testing & Documentation
- portal: Added integration tests to verify stack management and data synchronization across portals (
92cd3c0). - docs: Updated the README with new implementation examples and usage patterns (
3f7a314).
Resources
v1.2.0-0 (2026-02-15)
MeoNode UI v1.2.0-0 – Stack-Based Portal System
Release Date: 2026-02-15
Features & Refactors
- portal: Implemented a new stack-based architecture using
PortalProviderandPortalHostfor managed overlay stacks (7196f6f). - helper: Introduced
createDataChannelfor high-performance, subscription-based state updates (6508c0a). - hook: Added
usePortalanduseDataChannelhooks for reactive interaction with the new systems (b2f7d19). - core: Refactored
BaseNodeandNodeUtilfor better portal integration and removed legacy logic (91ced63).- BREAKING CHANGE: Legacy
portal.hoc.tshas been removed in favor of the new Provider architecture.
- BREAKING CHANGE: Legacy
Testing & Documentation
- portal: Added integration tests to verify stack management and data synchronization across portals (
92cd3c0). - docs: Updated the README with new implementation examples and usage patterns (
3f7a314).
Resources
v1.1.4 (2026-02-15)
MeoNode UI v1.1.4 – Style Registry & SSR Performance
Release Date: 2026-02-15
Fixes
- nextjs: Resolved duplicate style tag injection in
StyleRegistry(91bec99).- Renamed
registry.client.tstostyle-registry.client.ts. - Implemented SSR ID tracking to prevent redundant CSS content during Next.js streaming.
- Renamed
Testing & Quality
- emotion: Added style tag generation monitoring (
0576f0c).- Verified that nested
Divstructures efficiently batch styles into single tags. - Confirmed state changes correctly append new rules without breaking existing ones.
- Verified that nested
Resources
v1.1.3 (2026-02-14)
MeoNode UI v1.1.3 – Core Optimization & API Cleanup
Release Date: 2026-02-15
Features & Refactors
- hook: Removed deprecated
usePortaland strippedupdatemethods fromNodePortalandPortalHOC to streamline the API (eb40bd1). - core: Major logic optimization for performance (
27fdd1b):- Implemented static counters for deterministic
instanceIdgeneration. - Reduced array allocations in the iterative render loop.
- Switched
NodeUtil.isNodeInstancetoinstanceof BaseNodefor better performance. - Added WeakMap caching for function prop hashes in
NodeUtil.
- Implemented static counters for deterministic
Build & Tooling
- rollup: Switched CommonJS output to
.cjsextension inrollup.config.tsandpackage.jsonfor better ESM/CJS compatibility (7f6a130). - git: Updated
.gitignoreto include AI-specific directories (.claude) (0607574).
Dependency Updates
- deps: Updated
next,react,rollup, andtypescript-eslintto their latest versions (f95591e).
Resources
v1.1.2 (2025-12-22)
MeoNode UI v1.1.2 – ThemedCSSObject Nested Selectors
Release Date: 2025-12-22
Fix
- types: fix
ThemedCSSObjectto support nested selectors and theme functions in deeply nested objects (f938be5)- Updated
ThemedCSSObjectto use an intersection type with an index signature, allowing arbitrary string keys (like pseudo-selectors and media queries) to correctly resolve theme functions.
- Updated
Chore
- deps: update dependencies to latest versions (
51925b1)- Updated
nextto^16.1.0,react-router-domto^7.11.0,rollupto^4.54.0,@testing-library/reactto^16.3.1and@typescript/native-previewto^7.0.0.
- Updated
Version Update
- Current version: 1.1.2
Changelog & Commits
v1.1.1 (2025-12-05)
MeoNode UI v1.1.1 – Style Prop Type Fix
Release Date: 2025-12-05
Fix
- types: update HasCSSCompatibleStyleProp to use CSSProperties directly (
4e23499)
Version Update
- Current version: 1.1.1
Changelog & Commits
v1.1.0 (2025-12-05)
MeoNode UI v1.1.0 – Strict Types, Theme Features & Portal Optimizations
Release Date: 2025-12-05
Feat
- types: enforce strict component props and improve type inference (
047c0f8)- Redefined
MergedPropsand introducedValidateComponentPropsto strictly validate props passed toNodeandcreateNode. - Added
ExactPropsgeneric constraint to prevent excess property passing, improving type safety. - Enhanced
PropsOfto better infer props from React components, includingforwardRefandMemocomponents.
- Redefined
- theme: enable theme variable resolution in style keys (
30ca1fd)- Updated
ThemeUtilto resolve theme variables within style keys (e.g., media queries like@media (max-width: theme.breakpoint.lg)).
- Updated
Refactor
- portal: optimize portal implementation and fix types (
9643682)- Refactored
PortalHOC to use a cleaner, more efficient implementation for provider wrapping. - Improved type definitions for
PortalLauncherandPortalPropsto ensure correct prop inference.
- Refactored
Fix
- util: correct function child detection and rendering logic (
944cfbd)- Updated
isFunctionChildtype definition to correctly identify function children while excluding React components (Class, Memo, ForwardRef). - Updated
functionRenderertype definition to properly handle function child execution.
- Updated
Test
- test: refactor tests to align with strict types and fix leaks (
40c87c7)- Updated various tests (
advanced-features,leak-repro,memoization) to comply with the new strict type requirements. - Added
strict-component-props.test.tsandtheme-key-resolution.test.tsto verify new features.
- Updated various tests (
Chore
- build: update build config and dependencies (
a9367e0)- Updated
package.jsonscripts andtsconfig.jsonfor better build and test processes.
- Updated
Version Update
- Current version: 1.1.0
Changelog & Commits
v1.0.1 (2025-11-30)
MeoNode UI v1.0.1 – React Component Prop Handling Fix
Release Date: 2025-11-30
Fix
- core: fix style prop extraction for React Components (
19618b4)- Modified
processRawNodeto preventstyleprops from being extracted and flattened into HTML attributes when the node is a React Component. - This ensures components receive their
styleprop intact as an object, preventing invalid attribute errors.
- Modified
Version Update
- Current version: 1.0.1
Changelog & Commits
v1.0.0 (2025-11-28)
MeoNode UI v1.0.0 – Performance and Caching Enhancements
Release Date: 2025-11-28
Perf
- cache: Remove props, CSS, and theme caching to improve performance and reduce overhead. (
206361d), (d7baa16)
Fix
- theme-provider: Allow
setThemeto accept an updater function for more flexible state management. (be8d261) - core: Improve mount tracking for cached elements by ensuring
MeoNodeUnmounterwraps all renderable nodes. (d0ca27e) - theme-provider: Remove incorrect
@privateJSDoc tag fromThemeProvidercomponent. (816e398)
Test
- performance: Add controlled input performance tests to simulate human typing and measure
depsmemoization effectiveness. (bba48b8)
Docs
- readme: Update
README.mdto reflect the removal of automatic caching and emphasizedeps-based memoization. (2600d9c)
Version Update
- Current version: 1.0.0
Changelog & Commits
v1.0.0-0 (2025-11-28)
MeoNode UI v1.0.0-0 – Mount Tracking and Caching Overhaul
Release Date: 2025-11-27
Fix
- core: Overhaul mount tracking, caching, and fix stableKey generation to prevent memory leaks (
af1b707)- Replaced the simple
SetinMountTrackerUtilwith a reference-counting system (Map) to ensure a node is only considered unmounted when all its instances are gone. - The root element of a render cycle is now wrapped with a
MeoNodeUnmountercomponent before being cached to guarantee that the unmount logic is always present, even for cached elements. - Improved
stableKeygeneration inNodeUtil.createPropSignatureto correctly differentiate function props by hashing their string representation, preventing cache collisions for components with differentonClickor similar handlers. - Added new test suites (
leak-repro.test.ts,props-caching-leak.test.ts) to specifically target and verify the leak fixes.
- Replaced the simple
Perf
- cache: Improve props cache eviction strategy (
ce2f561)- Adjusted the
propProcessingCacheeviction logic inNodeUtilto be more aggressive, removing enough items to get back to theCACHE_SIZE_LIMITplus an additional buffer batch, preventing unbounded growth under high load.
- Adjusted the
Feat
- deps: Add react-router-dom and test polyfills (
29dcf13)- Introduced
react-router-domas a new development dependency to enable integration testing with React Router. - Added
whatwg-fetchand Node.jsutilpolyfills tojest.setup.tsfor compatibility in the Jest environment.
- Introduced
Test
- react-router: Add integration tests for react-router-dom (
8478623)- Introduced a new test suite to verify the proper functioning and caching behavior of MeoNode components when used within a React Router environment.
- Includes tests for declarative and programmatic navigation, ensuring that component lifecycles and caching mechanisms interact correctly with React Router's dynamic rendering.
Chore
- package: Rename publish:pre script to publish:prerelease and add publish:premajor script in package.json (
a98ba69)
Version Update
- Current version: 1.0.0-0
Changelog & Commits
v0.4.14 (2025-11-23)
MeoNode UI v0.4.14 – Performance and Caching Enhancements
Release Date: 2025-11-23
Perf
- cache: enforce dependency-based caching with shouldCacheElement helper (
fab5525)- Introduces NodeUtil.shouldCacheElement() helper to centralize and enforce the opt-in caching strategy where only nodes with explicit dependencies are cached
- Completes the memory optimization by closing loopholes where nodes without dependencies were still being cached based on stableKey alone
- Replaces 4 inconsistent cache eligibility checks in BaseNode.render():
- Cache lookup for parent nodes
- Cache lookup for child nodes
- Cache storage during rendering
- MeoNodeUnmounter wrapping decision
- Impact: Reduces memory usage, ensures mount tracking and cache operations stay in sync, improves code maintainability
Test
- memoization: refine test to assert precise cache size after rendering components (
8ded697)
Chore
- type: remove src/types/env.d.ts as it is no longer needed (aab4299)
Version Update
- Current version: 0.4.14
Changelog & Commits
v0.4.13 (2025-11-23)
MeoNode UI v0.4.13 – Props Handling and Leakage Fixes
Release Date: 2025-11-23
Fix
- props: improve prop handling and prevent leakage (
73cc696)- This commit refactors prop handling within the MeoNode ecosystem to ensure that internal processing props are not leaked to the DOM.
- Key changes:
- The
MeoNodeUnmounteris updated to correctly isolate and pass through props intended for the underlying DOM element, improving compatibility with libraries like MUI. - Internal props such as
node,css, anddisableEmotionare now explicitly prevented from being rendered as HTML attributes. - Added comprehensive tests to verify that standard HTML attributes are passed through while internal props are successfully filtered out.
- The
- This improves the robustness and predictability of component rendering.
Test
- props: add tests for prop handling and leakage (
a508e10)- Added new tests to verify that component props are correctly passed as HTML attributes, handle createNode and Node() correctly, and crucially, that internal MeoNode props are not leaked to the DOM.
Chore
- core: remove unnecessary type assertion from finalChildren assignment (
827b3ef)
Version Update
- Current version: 0.4.13
Changelog & Commits
v0.4.12 (2025-11-21)
MeoNode UI v0.4.12 – Build System Migration to Rollup
Release Date: 2025-11-21
Feat
- build: migrate from Babel to Rollup with ESM and CJS support (
70326a1)- Replace Babel build system with Rollup configuration to prevent output legacy javascript code
- Add support for both ESM and CJS output formats
- Update package.json exports to point to new build outputs
- Add Rollup plugins for TypeScript, commonjs, minification, and preserve directives
- Remove Babel-related dependencies and configuration files
- Update tsconfig.json to use 'preserve' module setting and bundler resolution
- Configure build to output to separate ESM and CJS directories
Version Update
- Current version: 0.4.12
Changelog & Commits
v0.4.11 (2025-11-21)
MeoNode UI v0.4.11 – Unmounter Enhancements, Performance Tests & Dependency Upgrades
Release Date: 2025-11-21
Fix
- core: enhance MeoNodeUnmounter cleanup logic and support additional props cloning (
02c17f7)- Refactor MeoNodeUnmounter to use useEffectEvent for stable cleanup on unmount
- Cleanup removes node from BaseNode.elementCache, untracks mount via MountTrackerUtil, unregisters from BaseNode.cacheCleanupRegistry, and clears lastSignature to prevent memory leaks
- Support cloning and forwarding additional props to valid React children elements
Refactor
- node.util: enhance documentation for utility methods and improve clarity (
ee42c24) - theme: reorder ThemeResolverCache methods for clarity (
cb842c8)- Moved
_generateCacheKeyand_evictmethods below the main logic inThemeResolverCachefor better readability and organization - Removed duplicate declaration of
_instanceproperty - Kept functionality unchanged, improving code structure and maintainability
- Moved
Test
- perf: add memory leak detection test for navigation cycles and improve formatMemory function (
ba139fc)- Added a new performance test to detect memory leaks during repeated navigation cycles between pages
- The test measures heap memory usage before, during, and after navigation, ensuring memory growth stays within acceptable limits
- Enhanced the formatMemory utility to correctly handle negative byte values and added JSDoc comments for clarity
- Removed an obsolete shallowly equal props performance test to streamline the test suite
- unmounter: add regression test for MeoNodeUnmounter to forward implicit props in MUI RadioGroup integration (
2ecaabd)- Added a test to ensure MeoNodeUnmounter correctly forwards props injected via React.cloneElement, addressing issues with libraries like MUI where RadioGroup injects 'checked' and 'onChange' into Radio components
- This prevents swallowing of props and verifies proper behavior of controlled radio inputs
- Also updated an existing cache size assertion to allow equality, reflecting improved mount tracking
- perf: update react-createelement comparison tests with 5000 nested nodes and fix typings (
b345ec0)- Changed rerender to use React.cloneElement<any> for proper typing
- Updated NUM_NODES constant to 5000 for nested structure tests
- Removed redundant comments about node count reduction to prevent stack overflow
Chore
- deps: upgrade devDependencies and update test scripts (
2ea128e)- add new devDependencies: @emotion/is-prop-valid@1.4.0, @emotion/styled@11.14.1, @mui/material@7.3.5, and related packages
- update yarn to version 4.11.0
- update test scripts to increase max-old-space-size to 8192 and include react-createelement-comparison.test.ts in test and perf runs
- update various package resolutions in yarn.lock to align with new versions and dependencies
- babel: add "builtIns": false to minify plugin configuration (
e16cdfb) - yarn: update yarnPath to version 4.11.0 (
ecb6c68)
Docs
- CONTRIBUTING: improve formatting and readability of contribution guidelines (
a7462ed)
Version Update
- Current version: 0.4.11
Changelog & Commits
v0.4.10 (2025-11-20)
MeoNode UI v0.4.10 – Performance & Unmounter Fixes
Release Date: 2025-11-20
Fixes
- unmounter: prevent redundant FinalizationRegistry callbacks (
59f5adf)- Explicitly unregister node from cacheCleanupRegistry on unmount
- Prevents double cleanup when node is both unmounted and GC'd
- Improves cleanup efficiency and reduces unnecessary registry callbacks
Perf
-
util: optimize prop processing and child handling (
be26488)- Conditional sort: only sort keys if length > 1 (eliminates unnecessary sorts)
- Replace for...in with Object.keys() iteration for better performance and safety
- Add fast path for single child processing (non-array or 1-element array)
- Avoid unnecessary array operations for common single-child case
- Performance impact:
- Reduced CPU overhead for small prop objects
- Faster iteration with Object.keys() vs for...in
- Eliminates array map() call for single child components
-
core: add exception safety and optimize render method (
5aad000)- Wrap render logic in try-finally to ensure renderContext is always released
- Null out workStack slots before releasing to help GC
- Pre-allocate finalChildren array to avoid resizing during iteration
- Replace non-null assertion with explicit error handling for better debugging
- Add object pooling for workStack and renderedElements (reduces GC pressure)
- Pool capped at 50 contexts with 2048-item limit to prevent memory issues
- Exception safety ensures cleanup even if rendering throws
Test
-
perf: add React comparison tests with memory tracking (
bc66d54)- Add comprehensive performance comparison between React.createElement and MeoNode
- Test both flat (10k nodes) and nested (5k nodes) structures
- Include memory usage measurements (initial + after 100 updates)
- Reduce nested nodes from 10k to 5k to prevent stack overflow with StyledRenderer
- Add table output with time and memory columns for clear comparison
- Add GC availability warning for accurate measurements
- Provides detailed performance and memory behavior insights during re-renders
- Avoids stack overflow in deeply nested test scenarios
-
performance: enhance performance metrics report formatting and adjust thresholds (
6f3abe4)- Improve table formatting for performance metrics
- Adjust test thresholds based on optimization results
- Enhanced reporting for better visibility into performance characteristics
Version Update
- Current version: 0.4.10
Changelog & Commits
v0.4.9 (2025-11-19)
MeoNode UI v0.4.9 – Performance, Build & Test Overhaul
Release Date: 2025-11-19
Feat
- build: add support for @tests alias and include tests folder in build and test configs (
4dfd165)
Perf
- core: optimize rendering, caching, and memory management (
4f599be)- Reworks the core rendering loop to use a more efficient, manually-managed work stack with exponential growth, reducing reallocations and improving performance for deep and wide node trees.
- Optimizes stable key generation by removing expensive shallow equality checks in favor of strict reference equality, significantly speeding up prop processing for cached components.
- Implements a high-performance MinHeap-based LRU cache eviction strategy for prop processing, replacing a slower sort-based method. This ensures that the most relevant props are kept in the cache with minimal overhead.
- Introduces CSS object hashing to accelerate prop signature generation, avoiding costly serialization of large style objects.
- Fixes several memory leaks by ensuring proper cleanup of node properties (lastSignature, lastPropsObj) and unregistering nodes from the cache cleanup registry upon eviction.
- Decouples element cache deletion from mount tracking to prevent race conditions and ensure reliable cleanup during component unmounting.
Refactor
- build: rename constants directory from constants to constant and update imports accordingly (
9531947)
Test
- perf: overhaul performance test suite (
e3bd6ac)- Introduces a new, structured performance reporting system using cli-table3 for clear, grouped metrics.
- Refactors the entire performance test suite into logical groups: Layout Rendering, Memory Management, and Prop Processing.
- Adds comprehensive memory leak detection tests for heavy state changes and repeated mount/unmount cycles, using forced garbage collection for more accurate heap analysis.
- Extracts the large, complex CSS object into a dedicated test constant for better separation of concerns.
- Updates memoization tests to align with the new, weighted LRU cache eviction scoring.
Chore
- deps: update dependencies including typescript-eslint to 8.47.0 and add cli-table3 0.6.5 (
6064555)
Version Update
- Current version: 0.4.9
Changelog & Commits
v0.4.8 (2025-11-19)
MeoNode UI v0.4.8 – Iterative Rendering, Stronger Types & Performance Testing
Release Date: 2025-11-18
Feat
- client: add generic type parameter to render function for stronger type safety (
90a770e) - core: improve BaseNode rendering with iterative traversal and memory optimizations (
1d5330a)- Introduce WorkItem interface to represent nodes in the iterative work stack during BaseNode rendering.
- Replace recursive render traversal with an iterative depth-first approach using a preallocated workStack array for better performance and reduced call stack usage.
- Implement dynamic resizing of workStack array to handle arbitrary tree sizes efficiently.
- Update BaseNode’s internal caching fields (
lastPropsRefandlastSignature) to be public and consistently used for stable key generation. - Modify MeoNodeUnmounter component to accept BaseNode instance and clear its
lastPropsRefandlastSignatureon unmount to prevent memory leaks. - Refine type annotations across node utilities and factory functions for stronger type safety (
Record<string, unknown>instead ofany). - Optimize critical prop extraction logic by replacing Set and startsWith checks with faster inline charCode comparisons.
- Clean up and clarify utility methods related to prop signature creation, shallow equality, and portal management.
- Improve node.util.ts by adjusting caching strategies, prop categorization, and React element handling for better robustness and maintainability.
Fixes
- navigation-cache-manager: add proper typing and global window declaration for cleanup flag (
6180d40)
Refactor
- improve typings and type safety in theme util and styled renderer (
dbe1f33)- Added explicit TypeScript types (e.g., CssProp, Record<string, unknown>) for variables and function signatures in
styled-renderer.client.tsandtheme.util.ts. - Updated cache maps to use more precise generic types for better type inference and safety.
- Enhanced
resolveObjWithThemeand related theme resolution logic with stronger typing and nullish coalescing. - Improved error handling for invalid theme path values.
- Applied copy-on-write pattern with properly typed arrays and objects during theme resolution.
- Strengthened type guards, e.g.,
isPlainObjecttype predicate. - Minor fixes to variable declarations with explicit types for clarity and consistency.
- Added explicit TypeScript types (e.g., CssProp, Record<string, unknown>) for variables and function signatures in
Chore
- babel: update preset-env targets and expand plugin exclusions (
f38cd24)- Set preset-env targets to support ES modules
- Enable bugfixes option
- Add multiple plugins to exclude list for better optimization
- Clean up formatting of root, alias, extensions, and exclude fields
Test
- performance: add comprehensive performance tests and metrics reporting (
c3d7a81)- Add detailed performance tests measuring render times for realistic layouts, 10,000 flat nodes, and 10,000 deeply nested nodes.
- Introduce a heavy state changes test to detect memory leaks and ensure responsiveness under frequent updates.
- Collect and log performance metrics including median render times and memory usage for analysis.
- Add tests for stableKey generation performance with identical, shallowly equal, unique, large, and complex CSS props.
- Enhance test suite with CSS styling for better visualization and interaction during tests.
- Improve cleanup and reporting to avoid resource leaks and provide clearer performance insights.
Version Update
- Current version: 0.4.8
Changelog & Commits
v0.4.7 (2025-11-16)
MeoNode UI v0.4.7 – Optimized Navigation Caching & Stable Key Handling
Release Date: 2025-11-17
Fixes
- core/cache: Optimize navigation cache debounce timing and enhance stableKey handling. (
fff6f207)- Dynamically adjust navigation cleanup debounce duration based on cache size to improve performance in large SPAs.
- Make
stableKeyand_lastSignatureoptional to correctly represent their potentially undefined state. - Refactor
_getStableKeyto returnundefinedon server instead of an empty string. - Optimize critical props extraction using the new
NodeUtil.extractCriticalPropshelper. - Improve client-side caching logic to skip unnecessary lookups when
stableKeyis absent. - Remove unused imports and redundant code in
node.util.ts, and enhanceshallowEqualimplementation. - Update
createPropSignatureto returnundefinedon server and usegetElementTypeNamedirectly. - Add detailed comments and refine
hashStringimplementation by combining FNV-1a and djb2 hashes for more robust key hashing.
Version Update
- Current version: 0.4.7
Changelog & Commits
v0.4.6 (2025-11-16)
MeoNode UI v0.4.6 – Enhanced Caching & Memoization with Safe Lifecycle Management
Release Date: 2025-11-17
Bug Fixes
- core/cache: Enhance memoization & caching system to prevent memory leaks and ensure safe lifecycle management. (
253d7d00) Introduces a robust, three-layered cleanup strategy to ensure cache integrity:- Lifecycle-driven Cleanup:
MeoNodeUnmountercomponent ensures that caches of unmounted nodes are immediately cleaned when components are removed. - Event-driven SPA Cleanup:
NavigationCacheManagerUtiltriggers debounced cache clearing during SPA navigations (pushState/popstate), preventing stale entries. - GC-driven Safety Net: Uses
FinalizationRegistryto automatically clean cache entries for objects that are garbage collected, providing an additional layer of memory safety.
- Lifecycle-driven Cleanup:
Refactor
- core: Migrate core logic from
src/helper/to a newsrc/util/directory for better separation of concerns. - core: Extract internal utility functions from
core.node.tsintonode.util.tsfor modularity and reusability. - core: Make internal caches on
BaseNodepublic to support externalized cache management and monitoring.
Chore
- tooling: Enable TypeScript's
strict: truemode and update the codebase to comply fully with strict type checks. - tooling: Add CommonJS (
require) exports inpackage.jsonfor improved module compatibility in different environments. - tooling: Update test scripts to include
NODE_OPTIONS='--expose-gc', allowing explicit garbage collection during tests for more accurate memory testing. - tests: Refine test suite by standardizing
afterEachhooks and updating memoization tests to directly validate internal cache behavior.
Version Update
- Current version: 0.4.6
Changelog & Commits
v0.4.5 (2025-11-15)
MeoNode UI v0.4.5 – Advanced Cache Management & Test Suite Restructure
Release Date: 2025-11-15
Features
- cache: Implemented a robust multi-layer cache management system with dedicated theme caching.
This update introduces a more defensive and introspective caching architecture:- Refactored
NavigationCacheManagerto improve stability under frequent invalidation cycles. - Added memory monitoring hooks to track cache pressure and trigger automated cleanup when thresholds are exceeded.
- Introduced LRU (Least Recently Used) eviction in
ThemeResolverCache, ensuring stale theme resolution paths are removed predictably. - Integrated LRU behaviors with the broader
BaseNodecache lifecycle, enabling synchronized clearing across all caching layers.
(9ed749f6)
- Refactored
Refactor
- test: Split the previously monolithic
node.test.tsinto multiple focused, domain-specific test files.
This reorganization increases maintainability, readability, and clarity across the test suite:- Introduced
memoization.test.tsto isolate tests related to dependency tracking, subtree memoization, and cache-collision handling. - Cleaned up noisy logs by suppressing a repetitive
console.errortriggered during a duplicate-key memoization test.
The suppression preserves test intent while preventing unnecessary noise in CI output. - Tests are now organized by functional concerns, improving long-term scalability of the suite.
(930f998e)
- Introduced
Fixes
- core: Ensured the
disableEmotionflag is properly forwarded to_renderProcessedNode, allowing consistent handling within the rendering pipeline.
This resolves inconsistencies in node processing when Emotion styling is disabled.
(b68e3d4)
Chore
- test: Adjusted performance test cleanup logic by removing automatic cache clearing within
afterEach.
This change ensures performance tests run under realistic caching behavior instead of artificially reset conditions.
(f72cea5e)
Version Update
- Current version: 0.4.5
Changelog & Commits
- View Full Changelog: https://github.com/l7aromeo/meonode-ui/blob/HEAD/CHANGELOG.md
- Browse All Commits: https://github.com/l7aromeo/meonode-ui/commits
v0.4.4 (2025-11-14)
MeoNode UI v0.4.4 – Intelligent Caching & Memory-Safe Rendering
Release Date: 2025-11-15
Perf
- core: Implemented intelligent caching and memory management (
0e5671b)
A sophisticated caching and memory-safety layer has been introduced to prevent memory leaks and improve SPA performance. The system now tracks mounted components, cleans up unmounted ones, and maintains a stable, efficient cache across navigation events.
Highlights:
- Navigation-aware Cache Eviction:
NavigationCacheManagerlistens for browser navigation events (popstate,pushState, etc.) and automatically purges stale cache entries from previous pages. - Mount Tracking: A new
MountTrackermaps all activeBaseNodeinstances, enabling precise eviction of unused cached elements. - Advanced Eviction Policies:
SafeCacheManagercan evict unmounted components, older inactive entries, and even perform emergency cleanup during memory pressure. - Memory-Safe Portal System: Portal implementation now uses
WeakMap, ensuring DOM roots and portal nodes are garbage-collectable once unused. - Improved Cache Metadata: Cache entries now track timestamps, access counts, and a
WeakRefto the node instance for smarter eviction. - Stable Cache Keys: Component identifiers now rely on
WeakMapinstead of component names, ensuring stability even under minification. - Comprehensive Integration Tests: Added tests covering cache collision, rapid navigation flows, Strict Mode compatibility, large-prop hashing, and LRU behavior.
Fix
- core: Simplified property assignment logic in common helpers (
312af57)
Refactor
- core: Added
ElementCacheEntryinterface for memoization and improvedcssprop typings (6a8381c)
Version Update
- Current version: 0.4.4
Changelog & Commits
-
View Full Changelog
https://github.com/l7aromeo/meonode-ui/blob/HEAD/CHANGELOG.md -
Browse All Commits
https://github.com/l7aromeo/meonode-ui/commits
v0.4.3 (2025-11-14)
MeoNode UI v0.4.3 – Code Quality & Style Handling Improvements
Release Date: 2025-11-14
Fixes
- core: Adjusted
isStyledComponentlogic to improve style handling (ff7a59e).
Refactor
- core: Simplified
_processPropsby removing style prop handling (b3570b4).
Documentation
- core: Added detailed comments to rendering methods for better code maintainability (
731c83e).
Version Update
- Current version: 0.4.3
Changelog & Commits
v0.4.2 (2025-11-14)
MeoNode UI v0.4.2 – Dependency Property Fix
Release Date: 2025-11-14
Fixes
- core: Removed
depsproperty from props to prevent it from being passed to element attributes (6b01cbe).
Version Update
- Current version: 0.4.2
Changelog & Commits
v0.4.1 (2025-11-14)
MeoNode UI v0.4.1 – Performance & Stability Improvements
Release Date: 2025-11-14
Performance
- core: Optimized prop processing and caching with new signature generation for improved rendering efficiency (
8cf0319).
Fixes
- core: Enhanced error handling and improved style property detection for more robust runtime behavior (
ca79c27).
Tests
- node: Enhanced dependency and memoization tests with real-world scenarios to ensure reliability in production use cases (
d7452fa).
Refactor
- types: Moved
node.type.tsto types directory and updated imports for better code organization (ccf769a).
Chore
- deps: Updated dependencies in package.json for improved stability and performance (
eba2108).
Version Update
- Current version: 0.4.1
Changelog & Commits
v0.4.0 (2025-11-13)
MeoNode UI v0.4.0 – Advanced Memoization & Caching System
Release Date: 2025-11-13
Features
- core: Implemented an advanced memoization and caching system to optimize rendering performance (
3b0a110):- Dependency-Based Memoization: Nodes can now be created with a dependency array, similar to React's
useMemo, to prevent unnecessary re-renders of the node and its entire subtree if dependencies have not changed. - Enhanced Prop Caching: The prop signature generation is now more robust, and the cache uses an advanced LRU eviction strategy to remain efficient.
- API Updates: The
Node,createNode,createChildrenFirstNode, andComponentHOCs have been updated to accept an optionaldepsarray to enable memoization.
- Dependency-Based Memoization: Nodes can now be created with a dependency array, similar to React's
Example Usage
const App = () => { const [state, setState] = useState({ user: 'John', role: 'Admin' }) const updateUser = () => setState(s => ({ ...s, user: 'Jane' })) const updateRole = () => setState(s => ({ ...s, role: 'Editor' })) return Div({ children: [ Div({ onClick: updateUser, children: 'Update User' }), Div({ onClick: updateRole, children: 'Update Role' }), // Static: never re-renders, regardless of state changes Div({ children: `Initial User: ${state.user}` }, []), // Dependent: re-renders only when `state.user` changes Div({ children: `User: ${state.user}; Role: ${state.role}` }, [state.user]), ], }).render() }
With dependency-based memoization, only nodes whose dependencies change are re-rendered — enabling fine-grained reactivity and significant performance improvements in large trees.
Tests
- core: Added a comprehensive suite of tests for the new memoization and caching system, covering dependency-based memoization, reactive and non-reactive children, complex state updates, and memoization of Higher-Order Components (HOCs) (
6bcd1b1).
Version Update
- Current version: 0.4.0
Changelog & Commits
v0.3.18 (2025-11-12)
MeoNode UI v0.3.18 – Prop Caching Refinement for Dynamic Props
Release Date: 2025-11-12
Fixes
- core: Refined prop caching mechanism to correctly handle dynamic props and prevent stale value reuse in iterative rendering (
4c0641e).
Version Update
- Current version: 0.3.18
Changelog & Commits
v0.3.17 (2025-11-12)
MeoNode UI v0.3.17 – Iterative Renderer, Prop Caching & TypeScript Configuration Improvements
Release Date: 2025-11-12
⚡ Performance
- core: Implemented iterative renderer and prop caching to reduce re-renders and improve overall rendering efficiency (
8a3a264). - helper: Refactored theme resolution logic for improved performance and more reliable cache correctness (
9614cb8).
🧹 Chore & Maintenance
- Fixed minor typo in
useThemeJSDoc comment (de0ddd9). - Added deprecation notice to
usePortalhook (planned for removal in future release) (f8a2923). - Updated
PortalPropstype definition to include a default generic type parameter (de73ba5). - Updated
isNodeInstancetype guard to useBaseNodeinstead ofNodeInstancefor more accurate inference (2c69d05). - scripts: Increased Jest stack size and fixed build commands for better CI reliability (
e046cdf). - Renamed
jest.config.mjs→jest.config.tsto improve TypeScript testing integration (a3213eb). - Updated
tsconfig.jsonto:
📦 Version Update
- Current version: 0.3.17
🔗 Changelog & Commits
v0.3.16 (2025-11-05)
MeoNode UI v0.3.16 – Built-in React Fragment, Type Enhancements & Extended Test Coverage
Release Date: 2025-11-05
What's New
-
React:
-
Core:
-
Testing:
- Added comprehensive tests for
Fragment,Suspense, andActivitycomponents to validate style and render behavior (2af386f).
- Added comprehensive tests for
Version Update
- Current version: 0.3.16
Changelog & Commits
v0.3.15 (2025-11-04)
MeoNode UI v0.3.15 – Emotion Styling Control & React Detection Enhancements
Release Date: 2025-11-04
What's New
- Core: Introduced the new
disableEmotionprop to allow developers to disable Emotion styling. This setting now propagates to all child nodes for fine-grained control over style rendering (377a9e9). - React Integration: Improved detection logic for
isContextProviderandisReactClassComponent, ensuring more accurate differentiation of React components (e8839e4).
Version Update
- Current version is 0.3.15.
Changelog & Commits
v0.3.14 (2025-10-30)
MeoNode UI v0.3.14 – Core Support for Suspense & Activity Components
Release Date: 2025-10-30
What's New
- Core: Added internal handling for
SuspenseandActivitycomponents (0f9fcb1).
Version Update
- Current version is 0.3.14.
Changelog & Commits
v0.3.13 (2025-10-30)
MeoNode UI v0.3.13 – Theme Function Output Fix
Release Date: 2025-10-30
Fixes
- Theme Helper: Fixed an issue where theme functions returning string values were not being processed correctly, ensuring consistent theme resolution across components (
286fd89).
Version Update
- Current version is 0.3.13.
Changelog & Commits
v0.3.12 (2025-10-23)
MeoNode UI v0.3.12 – Suspense Component
Release Date: 2025-10-23
What's New
- React: Added the new
Suspensecomponent along with detailed JSDoc for bothActivityandSuspensecomponents to improve clarity and developer experience (c1760fd).
Version Update
- Current version is 0.3.12.
Changelog & Commits
v0.3.11 (2025-10-19)
MeoNode UI v0.3.11 – New Activity Node, Theme Helper Improvements & Dependency Updates
Release Window: 2025-10-19
What's New
- Components: Added React Activity Node and exported it in the main module (
aadbc2d). - Theme Helper: Improved
resolveObjWithThemeto provide more reliable object type checking (da1ce4c).
Changes
- Dependencies: Updated dependencies in
package.jsonandyarn.lock(0c0ced6). - Code Style: Removed
'use strict'directive from multiple files (17d79dc).
Docs
- Contributing: Updated Node.js version requirement in
CONTRIBUTING.md(4c577c3).
Tests
- Node Handling: Added test case to ensure Node instances passed through props are preserved and correctly resolved through theme processing (
f4d1344).
Version Update
- Current version is 0.3.11.
Changelog & Commits
v0.3.10 (2025-10-10)
MeoNode UI v0.3.10 – BaseNode Re-hydration & Dependency Updates
Release Window: 2025-10-09
Fixes
- Core: Re-hydrated
BaseNodeinstances that lost their class identity during the React lifecycle, ensuring stable behavior across renders.
Changes
- Dependencies: Updated all dependencies to their latest versions in
package.jsonfor improved compatibility and stability.
Version Update
- Current version is 0.3.10.
Changelog & Commits
v0.3.9 (2025-09-30)
MeoNode UI v0.3.9 – Root Node Viewport Fix
Release Window: 2025-09-30
Fixes
- HTML Node: Updated
Rootnode defaultminHeightandminWidthto use dynamic viewport units, ensuring better responsiveness across devices.
Version Update
- Current version is 0.3.9.
Changelog & Commits
v0.2.22 (2025-09-29)
MeoNode UI v0.2.22 – Key & CSS Prop Refactor for Improved Rendering
Release Window: 2025-09-29
Refactors
- Core:
- Removed manual key generation logic, relying instead on React’s reconciliation for array children.
- Streamlined CSS prop handling within
BaseNodeandStyledRendererfor better integration with styling solutions. - Updated
createElementcalls to correctly spread children, supporting both single and multiple children.
Version Update
- Updated version to 0.2.22.
Changelog & Commits
v0.3.8 (2025-09-28)
MeoNode UI v0.3.8 – Theme-Aware Styling Functions
Release Date: 2025-09-29
What's New
- Styling: Added support for theme-aware functions for css props.
- Enables more dynamic styling, such as:
padding: 'theme.spacing.md', backgroundColor: theme => tinycolor(theme.system.primary.default).setAlpha(0.8).toString(), css: { color: theme => theme.system.primary.default, }
- Enables more dynamic styling, such as:
Refactors
- Core:
- Refactored style resolution logic (
resolveObjWithThemeandStyledRenderer) to selectively process thecssprop. - Ensures other props (like
children) are not processed, maintaining compatibility with Next.js Server Components.
- Refactored style resolution logic (
Version Update
- Incremented version to 0.3.8.
Changelog & Commits
v0.3.7 (2025-09-27)
MeoNode UI v0.3.7 – RSC Compatibility and CSS Prop Isolation
Release Date: 2025-09-27
Features
- RSC: Made client components like
ThemeProviderandStyleRegistrycompatible with React Server Components.
Refactors
- Node: Isolated the
cssprop to be passed exclusively to theStyledRenderer, preventing it from being passed down to the underlying HTML element.
Version Update
- Bumps version to 0.3.7.
Changelog & Commits
v0.3.6 (2025-09-27)
MeoNode UI v0.3.6 – Cookie Fix, Element Handling Refinements & Changelog Sync
Release Window: 2025-09-26 → 2025-09-27
Fixes
- Theme Cookie: Added cookie path to prevent duplicate theme cookie issues.
- Component Stability: Fixed component remounting problems when children content changed dynamically (e.g., during typing).
Refactors
- Core:
- Removed automatic key generation (
_generateKey) and all related logic. - Simplified
_processRawNodeby removing thenodeIndexparameter and complex case handling. - Updated
render()to spread array children as individual arguments tocreateElement. - Preserved only explicit non-null keys from original React elements.
- Removed automatic key generation (
Changes
- Changelog: Updated missing changelog entries to ensure version history is complete and consistent.
Version Update
- Consolidated updates from v0.3.4 → v0.3.6. Current version is 0.3.6.
Changelog & Commits
v0.3.3 (2025-09-26)
MeoNode UI v0.3.3 – Theme System Overhaul
Release Date: 2025-09-26
⚠️ Breaking Changes
- Theme System: Theme handling has been migrated to React Context.
- The old
themeprop on components (e.g.,Div({ theme: { ... } })) is no longer supported. - You must now wrap your application (or subtree) with the
ThemeProvider.
- The old
Example Migration
Before:
import { Div } from '@meonode/ui'; const App = () => { return Div({ theme: { // theme object }, children: 'Hello world!', }); };
After:
import { Div, ThemeProvider } from '@meonode/ui'; const App = () => { return ThemeProvider({ theme: { mode: 'light', // or 'dark' or any custom mode system: { base: { default: '#ffffff', content: '#000000', } } }, children: [ Div({ backgroundColor: 'theme.base', color: 'theme.content', children: 'Hello world!', }), ], }); };
What’s New (since v0.2.21)
v0.3.3
- Fix: Enhanced element type validation to allow createPortal to be passed as children.
v0.3.2
- Fix: Updated import paths for the Node component to improve module structure.
- Fix (ThemeProvider): Updated type imports and refined function return types for clarity.
v0.3.1
- Docs: Updated README with details on the new theme system.
v0.3.0
- Feature: Introduced a new theme system using React Context for cleaner and more flexible theme propagation.
- Refactor: Removed unused _childrenHash property from the Node class.
Version Update
- Incremented version to 0.3.3.
Changelog & Commits
v0.2.21 (2025-09-23)
MeoNode UI v0.2.21 – Core Robustness & Type Improvements
Release Date: 2025-09-23
Refactors
- Core: Made node processing logic static and more robust to improve reliability.
What's New
- Types:
- Updated
NodeFunctiontype to better support dynamic node content generation. - Updated
FunctionRendererPropsinterface to useNodeFunctionfor consistency.
- Updated
Fixes
- Component HOC: Enhanced node instance check in
Rendererfor improved stability. - Node Types: Removed unused
processRawNodeproperty fromNodetype.
Changes
- Package: Updated dependencies to their latest versions.
Version Update
- Incremented version to 0.2.21.
Changelog & Commits
v0.2.20 (2025-09-23)
MeoNode UI v0.2.20 – Serialization Optimization & Type Safety Fixes
Release Date: 2025-09-22
Refactors
- ObjHelper: Streamlined serialization logic to improve performance and maintainability.
Fixes
- ESLint: Enforced
no-unsafe-function-typerule to strengthen type safety. - Node Helper: Updated
nodeSignaturefunction to accept theChildrentype and improved handling of circular references.
Version Update
- Incremented version to 0.2.20.
Changelog & Commits
v0.2.19 (2025-09-22)
MeoNode UI v0.2.19 – Portal Refinements & Tooling Update
Release Date: 2025-09-21
Refactors
- Core:
- Simplified
PortalLaunchertype definition for improved readability. - Enhanced
usePortalwith better component creation and cleanup logic. - Removed redundant re-render logic in portal rendering for efficiency.
- Simplified
Tests
- Added unit tests for dynamic portal creation and management.
- Removed outdated dynamic portal test case in
node.test.ts.
Changes
- Updated Yarn to v4.10.2 and configured
yarnPathin.yarnrc.yml.
Version Update
- Incremented version to 0.2.19.
Changelog & Commits
v0.2.18 (2025-09-19)
MeoNode UI v0.2.18 – Portal Enhancements & Function Children Support
Release Date: 2025-09-19
What's New
- Core:
- Added support for function children with improved rendering logic.
- Enhanced portal system with an
updatemethod and newNodePortaltype.
- Hooks: Introduced
usePortalhook to enable reactive portal behavior tied to state changes.
Example
A simple example using usePortal to make a portal reactive to state updates:
import { Button, Div, Node, Portal, type PortalProps } from "@meonode/ui"; import { usePortal } from "@meonode/ui"; import { useState } from "react"; const PortalContent = ({ portal }: PortalProps<{ text: string }>) => { return Div({ children: [ "This is portal content!", state >= 1 ? text : "Initial text value", Button(`Update Portal (${state + 1})`, { onClick: () => setState((s: number) => s + 1), }), Button("Close Portal", { onClick: () => portal?.unmount(), }), ], }).render(); }; const MyPortal = () => { const [state, setState] = useState<number>(0); const { setPortal, createComponent } = usePortal([state]); const PortalContentComp = createComponent( ({ portal }: PortalProps<{ text: string }>) => PortalContent({ text: "Hello there" }), ); return Div({ children: [ Button("Open Portal", { onClick: () => { const portal = Portal<{ text: string }>(PortalContent)(); setPortal(portal); }, }), ], }).render(); };
Version Update
- Incremented version to 0.2.18.
Changelog & Commits
v0.2.17 (2025-09-14)
MeoNode UI v0.2.17 – Utility Enhancements & Core Streamlining
Release Date: 2025-09-14
What's New
- Helpers: Added
omitandomitUndefinedutility functions to create object copies without specified keys or undefined values. - Integration: Applied these utilities to manage
finalPropsfor both standard HTML tags and custom components.
Refactors
- Removed unnecessary type assertions for
childrenincore.node.ts. - Streamlined element creation logic in
core.node.tsfor cleaner and more efficient rendering.
Tests
- Added a test case for rendering a
Divnode using theComponentHOC. - Replaced
Divcomponents withContainerfor consistency in performance tests. - Added a test for rendering an empty-prop
Divnode innode.test.ts.
Version Update
- Incremented version to 0.2.17.
Changelog & Commits
v0.2.16 (2025-09-13)
MeoNode UI v0.2.16 – Core Optimizations & Type Refinements
Release Date: 2025-09-13
What's New
- Core:
- Introduced a static
_isServerproperty to optimize server-side checks in caching methods. - Removed
passedKeyfrom_functionRendererand simplified the function-as-child wrapper. - Enhanced
toPortalunmount behavior to properly clean up the portal container.
- Introduced a static
Refactors
- Updated
childrenprop to use theChildrentype, improving clarity and consistency across the codebase.
Version Update
- Incremented version to 0.2.16.
Changelog & Commits
v0.2.15 (2025-09-12)
MeoNode UI v0.2.15 – Performance & Stability Improvements
Release Date: 2025-09-12
What's New
- Cache: Implemented a hybrid caching strategy using
WeakMapandMapfor processed children, improving both performance and memory management. - Helper:
- Added
nodeSignaturefor stable node signatures. - Enhanced
createStableHashwith bounded traversal for safer, more predictable hashing. - Improved
ObjHelper.stringifyfor deterministic serialization with sorted keys and special type handling.
- Added
Refactors
- Improved type definitions and error handling in rendering methods to increase reliability and maintainability.
Version Update
- Incremented version to 0.2.15.
Changelog & Commits
v0.2.14 (2025-09-12)
MeoNode UI v0.2.14 – Performance Optimizations & API Simplification
Release Date: 2025-09-12
What's New
- Core (BaseNode): Added caching for processed children to improve rendering performance.
- Components: Introduced
Containeras an alias forDivto simplify general-purpose container usage.
Removed
- Utilities: Removed the
shallowEqualhelper fromcommon.helper.tsas part of API cleanup.
Fixes
- Rendering: Optimized existing key checks and improved handling of
nullvalues to boost performance and stability in rendering functions.
Version
- Bumped version to 0.2.14.
Changelog & Commits
v0.2.13 (2025-09-12)
MeoNode UI v0.2.13 – Testing & Developer Workflow Enhancements
Release Date: 2025-09-11
What's New
- Development: Added a pre-commit hook for automatic linting and testing, and updated
package.jsonscripts for a smoother workflow. - Testing: Introduced Jest configuration with TypeScript support to standardize the testing environment.
- Core (BaseNode): Added comprehensive unit tests to validate and safeguard core functionality.
Fixed
- Core: Remove stylable elements from NO_STYLE_TAGS to ensure only non-visual/metadata tags are excluded from CSS styling.
Version Update
- Incremented version to 0.2.13.
Changelog & Commits
v0.2.12 (2025-09-11)
MeoNode UI v0.2.12 – Critical RSC Bug Fixes
Release Date: 2025-09-11
What's Fixed
- Core (BaseNode): Removed the child processing cache to resolve a critical bug that caused infinite page loads in server-side rendering (RSC) environments.
- Helper: Corrected element type retrieval logic within the hashing function used for child node processing.
Version Update
- Incremented version to 0.2.12.
Changelog & Commits
v0.2.11 (2025-09-11)
MeoNode UI v0.2.11 – Improved Documentation & Child Processing Overhaul
Release Date: 2025-09-11
What's Enhanced
- Core (BaseNode):
- Expanded and clarified JSDoc documentation, covering prop processing, child handling, and rendering logic.
- Overhauled child processing and caching mechanism to boost server-side performance and fix a memory leak.
- Replaced object stringification with a more efficient hashing strategy for cache keys.
- Introduced a cache management policy for better memory handling.
What's Fixed
- Helper: Fixed an unnecessary string check in flexbox style processing.
- Core (BaseNode): Updated a function placeholder to follow the unused parameter naming convention.
Version Update
- Incremented version to 0.2.11.
Changelog & Commits
v0.2.10 (2025-09-11)
MeoNode UI v0.2.10 – New render API & Constants Update
Release Date: 2025-09-10
What's Added
- Core: Introduced a top-level
renderfunction for a cleaner and simpler API when mounting a MeoNode instance to the DOM. This abstracts away the need to manually callreact-dom/client.- Before:
import { createRoot } from 'react-dom/client'; import { Div } from '@meonode/ui'; const container = document.getElementById('root'); const root = createRoot(container); root.render(Div({ children: 'Hello' }).render()); - After:
import { Div } from '@meonode/ui'; import { render } from '@meonode/ui/client'; const container = document.getElementById('root'); render(Div({ children: 'Hello' }), container);
- Before:
- Constants: Added
NO_STYLE_TAGSarray andnoStyleTagsSetfor fast lookup of tags that should not receive styles.
What's Enhanced
- Core: Improved
StyledRendererintegration to check for no-style tags.
What's Changed
- Helper: Updated CSS property set to rely on constants and respect no-style tags.
- Package: Dependencies updated to latest versions.
- Directory: Renamed
data→constantsfor clarity. - File: Renamed
cssProperties.ts→cssProperties.const.tsto better reflect its purpose.
Version Update
- Incremented version to 0.2.10.
Changelog & Commits
v0.2.9 (2025-09-05)
MeoNode UI v0.2.9 – Type Inference & Autocomplete Fix
Release Date: 05/09/2025
What's Fixed
- Core (types): Corrected the
MergedPropsdefinition to restore accurate TypeScript inference and props autocomplete.- Resolved a regression from v0.2.8 where
createNodeandcreateChildrenFirstNodelost autocomplete support for props due to incorrect typing.
- Resolved a regression from v0.2.8 where
Version Update
- Bumped version to 0.2.9.
Changelog & Commits
v0.2.8 (2025-09-05)
MeoNode UI v0.2.8 – Unified Props Handling with MergedProps
Release Date: 05/09/2025
What's Changed
- Core (node): Refactored props handling to use a single utility type
MergedProps<E, AdditionalProps>, simplifying and unifying typing forcreateNodeandcreateChildrenFirstNode. - Improved developer experience for prebuilt components by allowing safer extension and overriding of props.
- Example:
import { createNode, Input } from '@meonode/ui' export const Div = createNode('div') // Add new props const custom = Div<{ field: string }>({ field: 'Hello' }) // Override existing React props const withOverride = Input<{ onChange: (e: { target: { value: string } }) }>({ onChange: ({ target }) => console.log(target.value), })
- Example:
- Helpers: Reorganized helper files and updated import paths.
Bug Fixes
- Styled Renderer: Made
childrenprop optional inStyledRendererProps.
Version Update
- Bumped version to 0.2.8.
Changelog & Commits
v0.2.7 (2025-09-04)
MeoNode UI v0.2.7 – Node Index Renaming & Key Logic Update
Release Date: 04/09/2025
What's Changed
- Core (node): Renamed
childIndextonodeIndexand refined key generation logic.
Bug Fixes
- No bug fixes in this release.
Version Update
- Bumped version to 0.2.7.
Changelog & Commits
v0.2.6 (2025-09-04)
MeoNode UI v0.2.6 – Dependency Updates & Key Generation Enhancements
Release Date: 04/09/2025
What’s Changed
- Dependencies: Updated TypeScript ESLint, native-preview, and JSDoc to their latest versions.
- Core (node): Improved generateIndexedKeyIfNeeded to support object parameters and enhance key uniqueness using children count.
- Imports: Continued adjustments to import paths, ensuring file extensions are included for full compatibility with ES modules.
Bug Fixes
- No bug fixes in this release.
Version Update
- Bumped version to 0.2.6.
Changelog & Commits
v0.2.5 (2025-09-04)
MeoNode UI v0.2.5 Release Notes
Release Date: 03/09/2025
What's Changed
- imports: Updated import paths to include file extensions, ensuring proper ESM (ECMAScript Module) compatibility across modern bundlers and runtimes.
Bug Fixes
- Fixed issues with ES compatibility caused by missing file extensions in imports.
Version Update
- Bumped version to 0.2.5.
Full Changelog
- See the Changelog for details: https://github.com/l7aromeo/meonode-ui/blob/HEAD/CHANGELOG.md
- View all commits: https://github.com/l7aromeo/meonode-ui/commits
v0.2.4 (2025-09-02)
MeoNode UI v0.2.4 Release Notes
Release Date: 02/09/2025
Feature Updates
- Exposed the original element via the created Node for easier access and debugging.
- You can now access the underlying element type from a Node (e.g.,
MyComp.element).
- You can now access the underlying element type from a Node (e.g.,
Bug Fixes
- No bug fixes in this release.
Version Update
- Bumped version to 0.2.4.
Full Changelog
- See the Changelog for details: https://github.com/l7aromeo/meonode-ui/blob/HEAD/CHANGELOG.md
- View all commits: https://github.com/l7aromeo/meonode-ui/commits
On this page
- Release Notes - MeoNode UI