• Getting Started
    • Overview
    • Why Without JSX?
    • Installation
    • Usage
    • Styling
    • Theming
    • Portal System
    • Rules & Patterns
    • Framework Integration
    • Compiler
    • FAQ
    • Release Notes
  • MUI Integration
  • Components
  • Hooks

Release Notes - MeoNode UI

This page is generated from the latest GitHub Releases.


3.1.3 (2026-09-27)

View release on GitHub

Patch Changes

  • #47 c3abb1b Thanks @l7aromeo! - A slow page no longer loses the server HTML of its Suspense boundaries when ThemeProvider hydrates (#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 #fragment link, 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: each useTheme reader, and each element whose css holds 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.

    PortalProvider had the same problem when a layer was opened while the page was still hydrating, from a mount effect for instance: opening one now re-renders PortalHost alone.

    A css theme function reading theme.mode inside 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)

View release on GitHub

Patch Changes

  • #43 6ade6d4 Thanks @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 StyleRegistry cache, and the cache a styled element outside any CacheProvider gets, 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 css key 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)

View release on GitHub

Patch Changes

  • #41 333a92e Thanks @l7aromeo! - Theme tokens in at-rule conditions and selectors resolve again in server components (#39).

    A node rendered from a server component, whose css put 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 the ThemeProvider above it. That part of the node's css is 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 its css is still compiled on the server. This covers host tags and function components alike, conditions nested inside selectors, css written 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 contains theme., such as '& .theme.accent', is left as written.

    A key with a theme token now keeps its place in the css object 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 ThemeProvider is 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)

View release on GitHub

Deprecated. In server components, 3.1.0 drops or breaks a css key whose at-rule condition or selector uses a theme token (#39). Upgrade to 3.1.2 or later.

Minor Changes

  • #36 af53324 Thanks @l7aromeo! - React Server Components: every server-compiled style now reaches the page, and host tags no longer ship their css in the flight payload.

    • Rules reach prerendered pages (#34). A node whose css is 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 whole css object, 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 className passed into a styled server component overrides its own css, 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, Radix asChild, 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 as Img, an svg, 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-server build of React exports no Component, and checking instanceof React.Component threw TypeError: Right-hand side of 'instanceof' is not an object for any child that reached it, including a Promise. Such children are now handed to React as written.

Patch Changes

  • #36 af53324 Thanks @l7aromeo! - The css prop: a call site extends a factory's css, every shape the type accepts renders, and theme values resolve the same on server and client.

    • A call-site css extends a factory's instead of replacing it (#31). createNode and createChildrenFirstNode combined props with a shallow spread, so a call site that added one rule dropped every pseudo-class, media query and @supports fallback 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 explicit undefined still drops one factory rule. When either side is an Emotion css() result, an array, a function or a string, the two are composed as [factoryCss, callSiteCss], so both apply and the call site wins. css: false or null at 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 css renders. 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 a css() result dropped the flat props and runtime defaults beside it. It is now composed after the flat props, so css still wins. A css map renders exactly as before.

    • Type: the top-level css prop no longer accepts a number or a ComponentSelector, which never produced a style as the whole value; both are still valid inside one. boolean, null and undefined are still accepted, so css: active && { … } works.

    • theme.* strings inside arrays (fontFamily: ['Arial', 'theme.font.body'], a string css, a string inside an array css) 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 ThemeProvider are 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 003c4a7 Thanks @l7aromeo! - Stop the default flex-shrink: 0 from overriding the flex shorthand (#33).

    A node that set flex: '1 1 auto' had flex-shrink: 0 emitted 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 the none, auto and initial keywords 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 as var(--flex) or inherit, gets no default flex-shrink at all, so the browser applies it as written.

    Behaviour change: flex: 0 (number or string) now emits flex-shrink: 1 instead of 0. CSS reads flex: 0 as 0 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 on flex: 0 not shrinking should set flex: '0 0 0%' or flexShrink: 0.

    flex: 1, auto, none, initial, an explicit flexShrink, and the defaults for flex containers and plain flex items emit exactly what they did before.

  • #29 3856136 Thanks @l7aromeo! - Name the missing prop instead of dying inside a layout effect.

    ThemeProvider called without modes threw Cannot 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 touched modes, so a first-time visitor was fine while anyone who had ever chosen a theme got a dead page.

    tokens, modes and defaultMode are 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 a defaultMode or defaultPreference that names a mode modes does not declare.

    The checks are not behind setDebugMode or 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 what themeScript already does with its own configuration.

  • #36 53083d5 Thanks @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 af53324 Thanks @l7aromeo! - Outside the React Server Components layer, function components with css render the same on the server as on the client.

    A function component given css (one built with createNode, an Emotion styled component, a MUI component) had its styles compiled on the server into a class of its own, handed to it as a plain className, while the client renders it through Emotion. The two disagreed: the server's class had a different prefix, a component passing the className on to a styled element kept two classes where the client merged them, and without Next's StyleRegistry the class's rule was never written. Hydration reported that the attributes did not match.

    In a plain renderToString or renderToPipeableStream app, 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 Emotion CacheProvider, and every class has its rule in the page.

  • #36 f97eb1e Thanks @l7aromeo! - StyleRegistry emits 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)

View release on GitHub

Major Changes

  • #26 ddd859e Thanks @l7aromeo! - One ThemeProvider, taking modes rather than a theme object.

    ThemeProvider({
      modes: ['light', 'dark'],
      defaultMode: 'light',
      defaultPreference: 'system', // optional
      system: { light: 'light', dark: 'dark' }, // optional
      tokens,
      children,
    })
    

    ThemeModesProvider is removed — its behaviour is what ThemeProvider now does. The theme: Theme shape 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 }) becomes ThemeProvider({ 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 be var(--…) references whose palettes live in CSS keyed by [data-theme="…"]; that is what makes a server-rendered document the same for every reader.

    setTheme is removed. setTheme(darkTheme) becomes setMode('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, setTheme could 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() returns theme, mode, preference, modes, setMode, setPreference and hydrated. hydrated is false until the provider has adopted the reader's real mode: the first client render must match the server's, so anything rendering markup from mode — 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)

View release on GitHub

Deprecated. @meonode/ui versions before 3.0.0 are no longer maintained. Upgrade to the latest 3.x.

Minor Changes

  • #24 793c4fb Thanks @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() gains mode, preference, setMode and setPreference beside what it already returned. Only mode is meaningful under ThemeProvider, where it reads the current theme's own mode; preference is absent there and the two setters throw, naming ThemeModesProvider. Changing a mode while keeping the theme's system would 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. preference is what the reader chose — possibly 'system' — and mode is 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 the system mapping is given, because prefers-color-scheme answers 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. useTheme no 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.

    ThemeModesProvider is the path to build on. ThemeProvider keeps 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 makes ThemeProvider mean this behaviour and the theme-swapping shape takes a legacy name or goes.

    Two components rather than two shapes on one, because createNode infers a component's props and a union of the two collapses to never there, which stops every existing call site compiling. Keeping them separate also means a half-configured provider — tokens without modes — 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. ThemeProvider itself is untouched.

    The provider renders defaultMode on its first pass — server and client alike — and adopts the reader's real mode in a layout effect, exposing hydrated on 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 from mode — 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, so hydrated exists 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.

    defaultPreference says what a reader who has never chosen starts on, which the mode cannot express: defaultMode: 'night' means "dark when nothing is stored", while defaultPreference: 'system' means "follow the OS until told otherwise". It defaults to defaultMode, a stored choice outranks it, and 'system' without a system mapping 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 MeoTheme gets them everywhere. The provider's props, the mapping in system, and everything useTheme() hands back — mode, modes, setMode, setPreference — take ResolvedThemeMode, which is MeoTheme['mode'] when augmented and the loose ThemeMode when 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 — defaultMode is a member of modes, 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 a data-theme no selector matches. Passing theme alongside tokens takes the mode path and says so once. A stored value that is not one of the declared modes is discarded for defaultMode, which is also what makes an upgrade safe: the original path writes localStorage.theme as 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.

    ThemeMode was 'light' | 'dark' | string, which collapses to string and lost even those two from autocomplete. It is now 'light' | 'dark' | (string & {}), which keeps them as hints without closing the set. MeoTheme augmentation of mode still overrides it.

  • #24 8ad3e5e Thanks @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 called a</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 its script-src 'sha256-…' is one value that never changes, rather than one per application that moves whenever somebody renames a mode.

    THEME_SCRIPT_CSP_HASH is 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-theme is 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-src contains THEME_SCRIPT_CSP_HASH. This catches a stale literal exactly, before a browser is involved.
    • no csp-violation report arrives. Measured against a deliberately stale hash, a ReportingObserver for csp-violation with buffered: true reports it with effectiveDirective: 'script-src-elem' even when it is registered after the page has loaded, which a test usually is. A securitypolicyviolation listener has to be in place before the parser reaches the script, so it is the wrong shape for this.
    • data-theme is present before hydration — read it at load, 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.

    defaultPreference is where a reader starts; defaultMode is 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, and defaultMode cannot express following it: it has to name a mode, and a mode may not be called system. So a site that wants to begin by asking the OS says defaultPreference: 'system', and still names a defaultMode for when nothing can answer — a blocked storage, a missing matchMedia, a stored mode that has since been renamed. Omitted, it is defaultMode, so nothing existing moves.

    A site that declares its modes gets them checked. modes, defaultMode, defaultPreference and both sides of system are typed as ResolvedThemeMode, which is MeoTheme['mode'] when a site augments it and the loose ThemeMode when 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 in modes is consistent with itself, so defaultMode is still a member of modes, 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 a data-precedence stylesheet 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 missing matchMedia — ends with no data-theme written. 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 suppressHydrationWarning on 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. localStorage throws on property access, not merely from getItem, where site data is blocked and in a sandboxed iframe without allow-same-origin; that read is guarded on its own so those readers still get the default rather than nothing. matchMedia is guarded separately too, falling back to the light name, which is what the provider falls back to as well. A stored system with no mapping resolves to the default instead of stamping the word system, which no palette matches.

    A configuration that cannot work throws. An empty modes, a defaultMode or a system value that is not one of them, a defaultPreference that is neither a mode nor 'system', a defaultPreference: 'system' with no mapping to say what the OS's words mean here, a mode named system, 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.

Patch Changes

  • #24 78fef6d Thanks @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 callSiteLocations option, 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)

View release on GitHub

Deprecated. @meonode/ui versions before 3.0.0 are no longer maintained. Upgrade to the latest 3.x.

Patch Changes

  • #21 9425c1b Thanks @l7aromeo! - Type children as the nesting the runtime already accepts.

    2.2.0 let a children array contain arrays, and Children was 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 const list is a valid children list:

    export type Children = NodeElement | readonly Children[]
    

    readonly costs existing callers nothing — a mutable array is assignable to a readonly one, and the patterns that read an array back out of a Children value go through Array.isArray, which narrows to any[] either way. Checked against the built .d.ts from a consumer project: mutating after a guard, returning one as NodeElement[], spreading one, and passing a NodeElement[] as children all compile before and after; only the nested form changes answer.

    The accompanying .test-d.ts is 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)

View release on GitHub

Deprecated. @meonode/ui versions before 3.0.0 are no longer maintained. Upgrade to the latest 3.x.

Minor Changes

  • #19 04959df Thanks @l7aromeo! - Say where an unkeyed list came from.

    React cannot answer that in a MeoNode app. Div({...}) only builds a node; createElement fires 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 callSiteLocations option 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 04959df Thanks @l7aromeo! - Let a children array contain arrays.

    Div({ children: [Span('a'), items.map(render)] }) used to throw Objects 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 04959df Thanks @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 unless setDebugMode happened to be on — and nobody discovers setDebugMode from 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 behind setDebugMode: 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() read process.env.NODE_ENV on every call, which in Node is a native getenv rather 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. setDebugMode still takes effect at runtime.

  • #19 04959df Thanks @l7aromeo! - Catch a node passed as a DOM attribute on a plain HTML tag.

    Div({ title: Span('x') }) rendered title="[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)

View release on GitHub

Deprecated. @meonode/ui versions before 3.0.0 are no longer maintained. Upgrade to the latest 3.x.

Minor Changes

  • #16 4d42425 Thanks @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/ui that 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 a key to 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 and For still fixes the problem.

    Both halves are required, and an old pairing is worse than no pairing. A compiler that emits __meo$list running against an earlier @meonode/ui produces no warnings at all — it produces console noise instead. No earlier runtime strips the key, so it reaches the element, and React rejects __meo$list as 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_KEYS entries for schemas 2 and 3 carry a list field. 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 children
    

    Separate 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 time Div({ 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/compiler sets __meo$list on 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, so children: 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 d377a51 Thanks @l7aromeo! - Add For, 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 time Div({ 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.

    For takes 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), matching Array.map rather than inverting it — the key is the one For applied, 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. For is the ergonomic answer to that warning, and unlike the warning it needs no compiler.

Patch Changes

  • #16 5769190 Thanks @l7aromeo! - Rebuild a memoized subtree when the node behind it is a different node.

    Every node given deps renders as a MeoMemo element, 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, and useMemo — seeing deps compare 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.element now 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 as useMemo([]) 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)

View release on GitHub

Deprecated. @meonode/ui versions before 3.0.0 are no longer maintained. Upgrade to the latest 3.x.

Patch Changes

  • #14 aeb8791 Thanks @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 and cache.inserted accumulated every style ever rendered. StyleRegistry flushes 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/hooks needs 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 is no-store because of the CSP nonce, so nothing can cache it away.

    StyleRegistry now 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. StyleRegistry is a client component, so it renders in the SSR pass, after the server components above it have already compiled their css — 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 themed Link on 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 bench is unchanged.


2.0.1 (2026-08-28)

View release on GitHub

Deprecated. @meonode/ui versions before 3.0.0 are no longer maintained. Upgrade to the latest 3.x.

Patch Changes

  • #12 09d74ba Thanks @l7aromeo! - Remove four internal members left behind when the element cache was replaced.

    NodeUtil.hashString, NodeUtil.hashCSS, NodeUtil.isStyleProp and NodeUtil.shouldNodeUpdate all 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, scripts and bench — its own definition — and zero times in @meonode/mui, @meonode/compiler or the documentation site. NodeUtil is 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)

View release on GitHub

Deprecated. @meonode/ui versions before 3.0.0 are no longer maintained. Upgrade to the latest 3.x.

Major Changes

  • #10 7482625 Thanks @l7aromeo! - Replace the element cache with fiber-backed memoization.

    A node given deps had its rendered element held in one process-global Map, 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/compiler narrowed 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 MeoMemo fiber holding useMemo(() => node.render(), deps). Identity is the fiber, so there is nothing to derive and nothing to collide, for plain function components and Component alike. Release is React dropping the fiber. deps semantics are unchanged.

    Faster, not slower, because the derived key is what cost the most:

    beforeafter
    node construction31.89 ms13.24 ms (2.4x)
    client render23.73 ms17.07 ms (1.39x)
    200 memoized rows x 31 renders14.85 ms8.74 ms (1.70x)
    entries left after unmount2000

    deps now 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 to useMemo, so deps: [] 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 MeoMemo is 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.clearCaches and Node.clearCaches
    • NodeInstance.signature and the deprecated NodeInstance.stableKey
    • NodeUtil.createPropSignature, NodeUtil.hashDynamicValues, NodeUtil.extractCriticalProps, NodeUtil.shouldCacheElement
    • MountTrackerUtil and NavigationCacheManagerUtil, including its history.pushState / replaceState patching
    • render()'s parentBlocked and scope parameters. render(container, node) from @meonode/ui/client no 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/compiler output stays compatible. __meo$k and __meo$dyn are 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.

Patch Changes

  • #9 20936ea Thanks @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 for main, so the beta line had no sanctioned publish path at all — leaving a laptop as the only option. @meonode/ui and @meonode/compiler set publishConfig.provenance, which npm can only satisfy from a provider it recognises, so a local changeset publish shipped @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 beta as well, so the prerelease line publishes the same way main does, with attestations intact. The trigger has to be listed on the default branch to take effect at all — GitHub reads a workflow_run workflow from there, so editing it on beta alone changes nothing.

    Added bun run release:dry, run in the release job before anything is published. npm publish --dry-run alone would not have caught this: it packs the tarball and reports success without ever evaluating publishConfig.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 means id-token: write, not merely "in CI"), and that no version is already on the registry — then defers to npm publish --dry-run for packing, files and auth.


2.0.0-beta.0 (2026-08-27)

View release on GitHub

Deprecated. @meonode/ui versions before 3.0.0 are no longer maintained. Upgrade to the latest 3.x.

Major Changes

  • 7482625 Thanks @l7aromeo! - Replace the element cache with fiber-backed memoization.

    A node given deps had its rendered element held in one process-global Map, 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/compiler narrowed 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 MeoMemo fiber holding useMemo(() => node.render(), deps). Identity is the fiber, so there is nothing to derive and nothing to collide, for plain function components and Component alike. Release is React dropping the fiber. deps semantics are unchanged.

    Faster, not slower, because the derived key is what cost the most:

    beforeafter
    node construction31.89 ms13.24 ms (2.4x)
    client render23.73 ms17.07 ms (1.39x)
    200 memoized rows x 31 renders14.85 ms8.74 ms (1.70x)
    entries left after unmount2000

    deps now 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 to useMemo, so deps: [] 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 MeoMemo is 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.clearCaches and Node.clearCaches
    • NodeInstance.signature and the deprecated NodeInstance.stableKey
    • NodeUtil.createPropSignature, NodeUtil.hashDynamicValues, NodeUtil.extractCriticalProps, NodeUtil.shouldCacheElement
    • MountTrackerUtil and NavigationCacheManagerUtil, including its history.pushState / replaceState patching
    • render()'s parentBlocked and scope parameters. render(container, node) from @meonode/ui/client no 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/compiler output stays compatible. __meo$k and __meo$dyn are 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.


1.8.7 (2026-08-02)

View release on GitHub

Deprecated. @meonode/ui versions before 3.0.0 are no longer maintained. Upgrade to the latest 3.x.

Patch Changes

  • #1 5d234ac Thanks @l7aromeo! - Point the package metadata at the new monorepo. repository, bugs and homepage referenced the three separate repositories that have now been merged into l7aromeo/meonode, and each package gains a repository.directory so 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)

View release on GitHub

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)

View release on GitHub

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)

View release on GitHub

Bug Fixes

  • portal: accept layer components that return a node (#28) (0455d3a)

v1.8.4 (2026-08-01)

View release on GitHub

Bug Fixes

  • types: keep component body props required while the call site stays flexible (#27) (94e1216)

v1.8.3 (2026-08-01)

View release on GitHub

Bug Fixes

  • core: keep the props shield intact across re-processing (#26) (40fe63a)

v1.8.2 (2026-08-01)

View release on GitHub

Bug Fixes

  • core: allow and honour key on Component nodes (#25) (fdcb97c)

v1.8.1 (2026-08-01)

View release on GitHub

Bug Fixes

  • core: stop disableEmotion propagation mutating the source node (#24) (bc645af)

v1.8.0 (2026-08-01)

View release on GitHub

Features

  • core: accept compiler call-site keys on call sites that cannot be partitioned (#21) (0fc83b4)

v1.7.8 (2026-08-01)

View release on GitHub

Bug Fixes

  • core: stop leaking theme and unknown marker fields into the DOM (#22) (6887c2a)

v1.7.7 (2026-08-01)

View release on GitHub

Performance Improvements

  • core: make node identity a render-time parameter (#19) (3613329)

v1.7.6 (2026-07-31)

View release on GitHub

Bug Fixes

  • navigation: restore native history methods when the manager stops (#15) (908046f)

v1.7.5 (2026-07-31)

View release on GitHub

Bug Fixes

  • navigation: stop throwing when the History API is unavailable (#14) (fa184a5)

v1.7.4 (2026-07-29)

View release on GitHub

Bug Fixes

  • cache: stop memoized subtrees rendering each other's content (#4) (4210d2e)

v1.7.3 (2026-07-29)

View release on GitHub

Performance Improvements

  • theme: skip the theme walk when there is nothing to resolve (#3) (5defc16)

v1.7.2 (2026-07-28)

View release on GitHub

Performance Improvements

  • props: stop allocating per node on the compiled marker path (#2) (7218cb6)

v1.7.1 (2026-07-27)

View release on GitHub

Performance Improvements

  • theme: make token conversion allocation-free when there is nothing to convert (#1) (60c341c)

v1.7.0 (2026-07-27)

View release on GitHub

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)

View release on GitHub

Features

  • compiler: add schema 2 marker contract with namespaced bucket keys (48a5fa7)

v1.7.0-beta.1 (2026-07-26)

View release on GitHub

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)

View release on GitHub

Bug Fixes

  • deps: update dev dependencies to latest versions (8608467)

v1.6.1 (2026-06-26)

View release on GitHub

Bug Fixes

  • types: allow nested css literals without as const (23942b1)

v1.6.0 (2026-06-25)

View release on GitHub

Features

  • core: add Emotion-style as element polymorphism (7dbd75d)

v1.5.3 (2026-06-25)

View release on GitHub

Bug Fixes

  • deps: update dependencies and align peer declarations (d17b2a4)

v1.5.2 (2026-05-10)

View release on GitHub

Bug Fixes

  • add missing WebkitAppRegion to CSS property constants (02028b0)

v1.5.1 (2026-04-26)

View release on GitHub

Bug Fixes

  • types: harden DeepPaths for augmented MeoTheme (b5ca3a9)

v1.5.0 (2026-04-26)

View release on GitHub

Features

  • types: add MeoTheme augmentation for typed theme tokens (36bce42)

v1.4.6 (2026-04-25)

View release on GitHub

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)

View release on GitHub

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)

View release on GitHub

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)

View release on GitHub

Bug Fixes

  • emotion: align server theme token resolution with client vars mode (9e9f9c4)

v1.4.4-beta.5 (2026-04-24)

View release on GitHub

Bug Fixes

  • emotion: prevent empty-css hydration class drift (45edd78)

v1.4.4-beta.4 (2026-04-24)

View release on GitHub

Bug Fixes

  • rsc: stop re-wrapping client-reference elements during child normalization (ebee585)

v1.4.4-beta.3 (2026-04-24)

View release on GitHub

Bug Fixes

  • rsc: keep client-reference styling on a single render path (25b8580)

v1.4.4-beta.2 (2026-04-24)

View release on GitHub

Bug Fixes

  • theme: keep client render output in CSS var mode (e95316b)

v1.4.4-beta.1 (2026-04-24)

View release on GitHub

Bug Fixes

  • rsc: emit theme tokens as CSS vars in server style output (7a07cc1)

v1.4.3 (2026-04-23)

View release on GitHub

Bug Fixes

  • exports: keep StyleRegistry out of core main entrypoint (58b3eb3)

v1.4.2 (2026-04-23)

View release on GitHub

Bug Fixes

  • rsc: resolve themed props in server-bypassed client reference path (551e11c)

v1.4.1 (2026-04-23)

View release on GitHub

Bug Fixes

  • node: avoid server crashes when reading client-reference marker flags (4bb2aaf)

v1.4.0 (2026-04-23)

View release on GitHub

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)

View release on GitHub

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)

View release on GitHub

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)

View release on GitHub

MeoNode UI v1.2.0 – Stack-Based Portal System

Release Date: 2026-02-15

Features & Refactors

  • portal: Implemented a new stack-based architecture using PortalProvider and PortalHost for managed overlay stacks (7196f6f).
  • helper: Introduced createDataChannel for high-performance, subscription-based state updates (6508c0a).
  • hook: Added usePortal and useDataChannel hooks for reactive interaction with the new systems (b2f7d19).
  • core: Refactored BaseNode and NodeUtil for better portal integration and removed legacy logic (91ced63).
    • BREAKING CHANGE: Legacy portal.hoc.ts has been removed in favor of the new Provider architecture.

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)

View release on GitHub

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 PortalProvider and PortalHost for managed overlay stacks (7196f6f).
  • helper: Introduced createDataChannel for high-performance, subscription-based state updates (6508c0a).
  • hook: Added usePortal and useDataChannel hooks for reactive interaction with the new systems (b2f7d19).
  • core: Refactored BaseNode and NodeUtil for better portal integration and removed legacy logic (91ced63).
    • BREAKING CHANGE: Legacy portal.hoc.ts has been removed in favor of the new Provider architecture.

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)

View release on GitHub

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.ts to style-registry.client.ts.
    • Implemented SSR ID tracking to prevent redundant CSS content during Next.js streaming.

Testing & Quality

  • emotion: Added style tag generation monitoring (0576f0c).
    • Verified that nested Div structures efficiently batch styles into single tags.
    • Confirmed state changes correctly append new rules without breaking existing ones.

Resources


v1.1.3 (2026-02-14)

View release on GitHub

MeoNode UI v1.1.3 – Core Optimization & API Cleanup

Release Date: 2026-02-15

Features & Refactors

  • hook: Removed deprecated usePortal and stripped update methods from NodePortal and Portal HOC to streamline the API (eb40bd1).
  • core: Major logic optimization for performance (27fdd1b):
    • Implemented static counters for deterministic instanceId generation.
    • Reduced array allocations in the iterative render loop.
    • Switched NodeUtil.isNodeInstance to instanceof BaseNode for better performance.
    • Added WeakMap caching for function prop hashes in NodeUtil.

Build & Tooling

  • rollup: Switched CommonJS output to .cjs extension in rollup.config.ts and package.json for better ESM/CJS compatibility (7f6a130).
  • git: Updated .gitignore to include AI-specific directories (.claude) (0607574).

Dependency Updates

  • deps: Updated next, react, rollup, and typescript-eslint to their latest versions (f95591e).

Resources


v1.1.2 (2025-12-22)

View release on GitHub

MeoNode UI v1.1.2 – ThemedCSSObject Nested Selectors

Release Date: 2025-12-22

Fix

  • types: fix ThemedCSSObject to support nested selectors and theme functions in deeply nested objects (f938be5)
    • Updated ThemedCSSObject to use an intersection type with an index signature, allowing arbitrary string keys (like pseudo-selectors and media queries) to correctly resolve theme functions.

Chore

  • deps: update dependencies to latest versions (51925b1)
    • Updated next to ^16.1.0, react-router-dom to ^7.11.0, rollup to ^4.54.0, @testing-library/react to ^16.3.1 and @typescript/native-preview to ^7.0.0.

Version Update

  • Current version: 1.1.2

Changelog & Commits


v1.1.1 (2025-12-05)

View release on GitHub

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)

View release on GitHub

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 MergedProps and introduced ValidateComponentProps to strictly validate props passed to Node and createNode.
    • Added ExactProps generic constraint to prevent excess property passing, improving type safety.
    • Enhanced PropsOf to better infer props from React components, including forwardRef and Memo components.
  • theme: enable theme variable resolution in style keys (30ca1fd)
    • Updated ThemeUtil to resolve theme variables within style keys (e.g., media queries like @media (max-width: theme.breakpoint.lg)).

Refactor

  • portal: optimize portal implementation and fix types (9643682)
    • Refactored Portal HOC to use a cleaner, more efficient implementation for provider wrapping.
    • Improved type definitions for PortalLauncher and PortalProps to ensure correct prop inference.

Fix

  • util: correct function child detection and rendering logic (944cfbd)
    • Updated isFunctionChild type definition to correctly identify function children while excluding React components (Class, Memo, ForwardRef).
    • Updated functionRenderer type definition to properly handle function child execution.

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.ts and theme-key-resolution.test.ts to verify new features.

Chore

  • build: update build config and dependencies (a9367e0)
    • Updated package.json scripts and tsconfig.json for better build and test processes.

Version Update

  • Current version: 1.1.0

Changelog & Commits


v1.0.1 (2025-11-30)

View release on GitHub

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 processRawNode to prevent style props from being extracted and flattened into HTML attributes when the node is a React Component.
    • This ensures components receive their style prop intact as an object, preventing invalid attribute errors.

Version Update

  • Current version: 1.0.1

Changelog & Commits


v1.0.0 (2025-11-28)

View release on GitHub

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 setTheme to accept an updater function for more flexible state management. (be8d261)
  • core: Improve mount tracking for cached elements by ensuring MeoNodeUnmounter wraps all renderable nodes. (d0ca27e)
  • theme-provider: Remove incorrect @private JSDoc tag from ThemeProvider component. (816e398)

Test

  • performance: Add controlled input performance tests to simulate human typing and measure deps memoization effectiveness. (bba48b8)

Docs

  • readme: Update README.md to reflect the removal of automatic caching and emphasize deps-based memoization. (2600d9c)

Version Update

  • Current version: 1.0.0

Changelog & Commits


v1.0.0-0 (2025-11-28)

View release on GitHub

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 Set in MountTrackerUtil with 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 MeoNodeUnmounter component before being cached to guarantee that the unmount logic is always present, even for cached elements.
    • Improved stableKey generation in NodeUtil.createPropSignature to correctly differentiate function props by hashing their string representation, preventing cache collisions for components with different onClick or similar handlers.
    • Added new test suites (leak-repro.test.ts, props-caching-leak.test.ts) to specifically target and verify the leak fixes.

Perf

  • cache: Improve props cache eviction strategy (ce2f561)
    • Adjusted the propProcessingCache eviction logic in NodeUtil to be more aggressive, removing enough items to get back to the CACHE_SIZE_LIMIT plus an additional buffer batch, preventing unbounded growth under high load.

Feat

  • deps: Add react-router-dom and test polyfills (29dcf13)
    • Introduced react-router-dom as a new development dependency to enable integration testing with React Router.
    • Added whatwg-fetch and Node.js util polyfills to jest.setup.ts for compatibility in the Jest environment.

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)

View release on GitHub

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)

View release on GitHub

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 MeoNodeUnmounter is 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, and disableEmotion are 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.
    • 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)

View release on GitHub

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)

View release on GitHub

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 _generateCacheKey and _evict methods below the main logic in ThemeResolverCache for better readability and organization
    • Removed duplicate declaration of _instance property
    • Kept functionality unchanged, improving code structure and maintainability

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)

View release on GitHub

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)

View release on GitHub

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)

View release on GitHub

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 (lastPropsRef and lastSignature) to be public and consistently used for stable key generation.
    • Modify MeoNodeUnmounter component to accept BaseNode instance and clear its lastPropsRef and lastSignature on unmount to prevent memory leaks.
    • Refine type annotations across node utilities and factory functions for stronger type safety (Record<string, unknown> instead of any).
    • 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.ts and theme.util.ts.
    • Updated cache maps to use more precise generic types for better type inference and safety.
    • Enhanced resolveObjWithTheme and 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., isPlainObject type predicate.
    • Minor fixes to variable declarations with explicit types for clarity and consistency.

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)

View release on GitHub

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 stableKey and _lastSignature optional to correctly represent their potentially undefined state.
    • Refactor _getStableKey to return undefined on server instead of an empty string.
    • Optimize critical props extraction using the new NodeUtil.extractCriticalProps helper.
    • Improve client-side caching logic to skip unnecessary lookups when stableKey is absent.
    • Remove unused imports and redundant code in node.util.ts, and enhance shallowEqual implementation.
    • Update createPropSignature to return undefined on server and use getElementTypeName directly.
    • Add detailed comments and refine hashString implementation 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)

View release on GitHub

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:
    1. Lifecycle-driven Cleanup: MeoNodeUnmounter component ensures that caches of unmounted nodes are immediately cleaned when components are removed.
    2. Event-driven SPA Cleanup: NavigationCacheManagerUtil triggers debounced cache clearing during SPA navigations (pushState/popstate), preventing stale entries.
    3. GC-driven Safety Net: Uses FinalizationRegistry to automatically clean cache entries for objects that are garbage collected, providing an additional layer of memory safety.

Refactor

  • core: Migrate core logic from src/helper/ to a new src/util/ directory for better separation of concerns.
  • core: Extract internal utility functions from core.node.ts into node.util.ts for modularity and reusability.
  • core: Make internal caches on BaseNode public to support externalized cache management and monitoring.

Chore

  • tooling: Enable TypeScript's strict: true mode and update the codebase to comply fully with strict type checks.
  • tooling: Add CommonJS (require) exports in package.json for 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 afterEach hooks 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)

View release on GitHub

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 NavigationCacheManager to 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 BaseNode cache lifecycle, enabling synchronized clearing across all caching layers.
      (9ed749f6)

Refactor

  • test: Split the previously monolithic node.test.ts into multiple focused, domain-specific test files.
    This reorganization increases maintainability, readability, and clarity across the test suite:
    • Introduced memoization.test.ts to isolate tests related to dependency tracking, subtree memoization, and cache-collision handling.
    • Cleaned up noisy logs by suppressing a repetitive console.error triggered 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)

Fixes

  • core: Ensured the disableEmotion flag 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


v0.4.4 (2025-11-14)

View release on GitHub

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: NavigationCacheManager listens for browser navigation events (popstate, pushState, etc.) and automatically purges stale cache entries from previous pages.
  • Mount Tracking: A new MountTracker maps all active BaseNode instances, enabling precise eviction of unused cached elements.
  • Advanced Eviction Policies: SafeCacheManager can 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 WeakRef to the node instance for smarter eviction.
  • Stable Cache Keys: Component identifiers now rely on WeakMap instead 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 ElementCacheEntry interface for memoization and improved css prop typings (6a8381c)

Version Update

  • Current version: 0.4.4

Changelog & Commits


v0.4.3 (2025-11-14)

View release on GitHub

MeoNode UI v0.4.3 – Code Quality & Style Handling Improvements

Release Date: 2025-11-14

Fixes

  • core: Adjusted isStyledComponent logic to improve style handling (ff7a59e).

Refactor

  • core: Simplified _processProps by 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)

View release on GitHub

MeoNode UI v0.4.2 – Dependency Property Fix

Release Date: 2025-11-14

Fixes

  • core: Removed deps property 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)

View release on GitHub

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.ts to 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)

View release on GitHub

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, and Component HOCs have been updated to accept an optional deps array to enable memoization.

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)

View release on GitHub

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)

View release on GitHub

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 useTheme JSDoc comment (de0ddd9).
  • Added deprecation notice to usePortal hook (planned for removal in future release) (f8a2923).
  • Updated PortalProps type definition to include a default generic type parameter (de73ba5).
  • Updated isNodeInstance type guard to use BaseNode instead of NodeInstance for more accurate inference (2c69d05).
  • scripts: Increased Jest stack size and fixed build commands for better CI reliability (e046cdf).
  • Renamed jest.config.mjs → jest.config.ts to improve TypeScript testing integration (a3213eb).
  • Updated tsconfig.json to:
    • Exclude dist and node_modules directories (eeb9577).
    • Enhance compiler options and add path mappings for a smoother development experience (89bc1a4).

📦 Version Update

  • Current version: 0.3.17

🔗 Changelog & Commits


v0.3.16 (2025-11-05)

View release on GitHub

MeoNode UI v0.3.16 – Built-in React Fragment, Type Enhancements & Extended Test Coverage

Release Date: 2025-11-05

What's New

  • React:

    • Added the built-in React Fragment component (d5e376a).
    • Added REACT_ACTIVITY_TYPE constant to the react-is helper for consistent React element detection (e91e48f).
  • Core:

    • Enhanced NodeProps type to conditionally include built-in React components, improving type safety and flexibility (3b8a4cb).
    • Exported NO_STYLE_TAGS type for better type inference when disabling emotion styling (a6db6e8).
  • Testing:

    • Added comprehensive tests for Fragment, Suspense, and Activity components to validate style and render behavior (2af386f).

Version Update

  • Current version: 0.3.16

Changelog & Commits


v0.3.15 (2025-11-04)

View release on GitHub

MeoNode UI v0.3.15 – Emotion Styling Control & React Detection Enhancements

Release Date: 2025-11-04

What's New

  • Core: Introduced the new disableEmotion prop 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 isContextProvider and isReactClassComponent, ensuring more accurate differentiation of React components (e8839e4).

Version Update

  • Current version is 0.3.15.

Changelog & Commits


v0.3.14 (2025-10-30)

View release on GitHub

MeoNode UI v0.3.14 – Core Support for Suspense & Activity Components

Release Date: 2025-10-30

What's New

  • Core: Added internal handling for Suspense and Activity components (0f9fcb1).

Version Update

  • Current version is 0.3.14.

Changelog & Commits


v0.3.13 (2025-10-30)

View release on GitHub

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)

View release on GitHub

MeoNode UI v0.3.12 – Suspense Component

Release Date: 2025-10-23

What's New

  • React: Added the new Suspense component along with detailed JSDoc for both Activity and Suspense components to improve clarity and developer experience (c1760fd).

Version Update

  • Current version is 0.3.12.

Changelog & Commits


v0.3.11 (2025-10-19)

View release on GitHub

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 resolveObjWithTheme to provide more reliable object type checking (da1ce4c).

Changes

  • Dependencies: Updated dependencies in package.json and yarn.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)

View release on GitHub

MeoNode UI v0.3.10 – BaseNode Re-hydration & Dependency Updates

Release Window: 2025-10-09

Fixes

  • Core: Re-hydrated BaseNode instances 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.json for improved compatibility and stability.

Version Update

  • Current version is 0.3.10.

Changelog & Commits


v0.3.9 (2025-09-30)

View release on GitHub

MeoNode UI v0.3.9 – Root Node Viewport Fix

Release Window: 2025-09-30

Fixes

  • HTML Node: Updated Root node default minHeight and minWidth to 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)

View release on GitHub

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 BaseNode and StyledRenderer for better integration with styling solutions.
    • Updated createElement calls 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)

View release on GitHub

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,
      }
      

Refactors

  • Core:
    • Refactored style resolution logic (resolveObjWithTheme and StyledRenderer) to selectively process the css prop.
    • Ensures other props (like children) are not processed, maintaining compatibility with Next.js Server Components.

Version Update

  • Incremented version to 0.3.8.

Changelog & Commits


v0.3.7 (2025-09-27)

View release on GitHub

MeoNode UI v0.3.7 – RSC Compatibility and CSS Prop Isolation

Release Date: 2025-09-27

Features

  • RSC: Made client components like ThemeProvider and StyleRegistry compatible with React Server Components.

Refactors

  • Node: Isolated the css prop to be passed exclusively to the StyledRenderer, 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)

View release on GitHub

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 _processRawNode by removing the nodeIndex parameter and complex case handling.
    • Updated render() to spread array children as individual arguments to createElement.
    • Preserved only explicit non-null keys from original React elements.

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)

View release on GitHub

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 theme prop on components (e.g., Div({ theme: { ... } })) is no longer supported.
    • You must now wrap your application (or subtree) with the ThemeProvider.

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)

View release on GitHub

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 NodeFunction type to better support dynamic node content generation.
    • Updated FunctionRendererProps interface to use NodeFunction for consistency.

Fixes

  • Component HOC: Enhanced node instance check in Renderer for improved stability.
  • Node Types: Removed unused processRawNode property from Node type.

Changes

  • Package: Updated dependencies to their latest versions.

Version Update

  • Incremented version to 0.2.21.

Changelog & Commits


v0.2.20 (2025-09-23)

View release on GitHub

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-type rule to strengthen type safety.
  • Node Helper: Updated nodeSignature function to accept the Children type and improved handling of circular references.

Version Update

  • Incremented version to 0.2.20.

Changelog & Commits


v0.2.19 (2025-09-22)

View release on GitHub

MeoNode UI v0.2.19 – Portal Refinements & Tooling Update

Release Date: 2025-09-21

Refactors

  • Core:
    • Simplified PortalLauncher type definition for improved readability.
    • Enhanced usePortal with better component creation and cleanup logic.
    • Removed redundant re-render logic in portal rendering for efficiency.

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 yarnPath in .yarnrc.yml.

Version Update

  • Incremented version to 0.2.19.

Changelog & Commits


v0.2.18 (2025-09-19)

View release on GitHub

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 update method and new NodePortal type.
  • Hooks: Introduced usePortal hook 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)

View release on GitHub

MeoNode UI v0.2.17 – Utility Enhancements & Core Streamlining

Release Date: 2025-09-14

What's New

  • Helpers: Added omit and omitUndefined utility functions to create object copies without specified keys or undefined values.
  • Integration: Applied these utilities to manage finalProps for both standard HTML tags and custom components.

Refactors

  • Removed unnecessary type assertions for children in core.node.ts.
  • Streamlined element creation logic in core.node.ts for cleaner and more efficient rendering.

Tests

  • Added a test case for rendering a Div node using the Component HOC.
  • Replaced Div components with Container for consistency in performance tests.
  • Added a test for rendering an empty-prop Div node in node.test.ts.

Version Update

  • Incremented version to 0.2.17.

Changelog & Commits


v0.2.16 (2025-09-13)

View release on GitHub

MeoNode UI v0.2.16 – Core Optimizations & Type Refinements

Release Date: 2025-09-13

What's New

  • Core:
    • Introduced a static _isServer property to optimize server-side checks in caching methods.
    • Removed passedKey from _functionRenderer and simplified the function-as-child wrapper.
    • Enhanced toPortal unmount behavior to properly clean up the portal container.

Refactors

  • Updated children prop to use the Children type, improving clarity and consistency across the codebase.

Version Update

  • Incremented version to 0.2.16.

Changelog & Commits


v0.2.15 (2025-09-12)

View release on GitHub

MeoNode UI v0.2.15 – Performance & Stability Improvements

Release Date: 2025-09-12

What's New

  • Cache: Implemented a hybrid caching strategy using WeakMap and Map for processed children, improving both performance and memory management.
  • Helper:
    • Added nodeSignature for stable node signatures.
    • Enhanced createStableHash with bounded traversal for safer, more predictable hashing.
    • Improved ObjHelper.stringify for deterministic serialization with sorted keys and special type handling.

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)

View release on GitHub

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 Container as an alias for Div to simplify general-purpose container usage.

Removed

  • Utilities: Removed the shallowEqual helper from common.helper.ts as part of API cleanup.

Fixes

  • Rendering: Optimized existing key checks and improved handling of null values to boost performance and stability in rendering functions.

Version

  • Bumped version to 0.2.14.

Changelog & Commits


v0.2.13 (2025-09-12)

View release on GitHub

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.json scripts 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)

View release on GitHub

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)

View release on GitHub

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)

View release on GitHub

MeoNode UI v0.2.10 – New render API & Constants Update

Release Date: 2025-09-10

What's Added

  • Core: Introduced a top-level render function for a cleaner and simpler API when mounting a MeoNode instance to the DOM. This abstracts away the need to manually call react-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);
      
  • Constants: Added NO_STYLE_TAGS array and noStyleTagsSet for fast lookup of tags that should not receive styles.

What's Enhanced

  • Core: Improved StyledRenderer integration 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 → constants for clarity.
  • File: Renamed cssProperties.ts → cssProperties.const.ts to better reflect its purpose.

Version Update

  • Incremented version to 0.2.10.

Changelog & Commits


v0.2.9 (2025-09-05)

View release on GitHub

MeoNode UI v0.2.9 – Type Inference & Autocomplete Fix

Release Date: 05/09/2025

What's Fixed

  • Core (types): Corrected the MergedProps definition to restore accurate TypeScript inference and props autocomplete.
    • Resolved a regression from v0.2.8 where createNode and createChildrenFirstNode lost autocomplete support for props due to incorrect typing.

Version Update

  • Bumped version to 0.2.9.

Changelog & Commits


v0.2.8 (2025-09-05)

View release on GitHub

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 for createNode and createChildrenFirstNode.
  • 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),
      })
      
  • Helpers: Reorganized helper files and updated import paths.

Bug Fixes

  • Styled Renderer: Made children prop optional in StyledRendererProps.

Version Update

  • Bumped version to 0.2.8.

Changelog & Commits


v0.2.7 (2025-09-04)

View release on GitHub

MeoNode UI v0.2.7 – Node Index Renaming & Key Logic Update

Release Date: 04/09/2025

What's Changed

  • Core (node): Renamed childIndex to nodeIndex and 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)

View release on GitHub

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)

View release on GitHub

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


v0.2.4 (2025-09-02)

View release on GitHub

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).

Bug Fixes

  • No bug fixes in this release.

Version Update

  • Bumped version to 0.2.4.

Full Changelog


On this page