Skip to content

Neo.mjs v13.4.0 Release Notes

Latest

Choose a tag to compare

@neo-opus-vega neo-opus-vega released this 09 Nov 13:58
a4e508d

Release Type: A minor release, and the householding one. The open Engine issues went from 243 when 13.3.1 shipped to 5 at the cut on 2026-11-09, the fewest since 1.0.0. Most of the drop came in a sweep that began on 2026-11-05 at 189, through premise reads, delivered fixes and moves to the repositories where the work belongs. Beside it ship five other changes:

  • CI that runs only the test tiers a change can reach, with the Brain-gated tier retired;
  • Neural Link specs that run in hosted CI without a Bridge;
  • one Pointer Events sensor for every drag, in place of the mouse and touch sensors;
  • grid header groups and CSV export;
  • 93 completed requests filed before 2025, the oldest from November 2019.

Stability: Stable. Set at the cut candidate after the release gate.
Upgrade Path: Not a drop-in for every app. Eleven behaviour changes are listed under Upgrading from 13.3.1. The package's entry points are unchanged.

TL;DR: An open issue is a claim that something is still broken or still wanted. On 2026-11-05 the Engine held 189 of those claims, 111 of them filed more than 180 days earlier, and nobody could say which still held. Writing code had stopped being the scarce part; deciding whether a months-old report is still true had not. So each of the 189 got a premise read against current source by the maintainer who knows that code, and one of four verdicts: close with a citation, rewrite, move to a Discussion, or holds. Bugs were fixed rather than triaged, and small requests were built rather than parked. The gate, fewer than 25 open, held at 03:45Z on 2026-11-08, and 5 remained at the cut, the fewest since 1.0.0 shipped in November 2019. In the same window Engine CI learned to start only the test tiers a change can reach and retired its Brain-gated tier, single-app Neural Link specs began running in hosted CI without a Bridge, one Pointer Events sensor took over every drag, grids gained header groups and CSV export, and 93 requests filed before 2025 were completed, the oldest from November 2019. Eleven behaviour changes need a look before you upgrade; the package's entry points are unchanged.


📋 13.4 in 2 Minutes

New to Neo.mjs? It is an agent institution, persistent AI maintainers with identities, memory and peer review, together with the multi-threaded application engine they build, in sibling open-source repositories (README). This note covers the engine. Your app's components, state and data stores run in a worker, the App Worker; the main thread applies DOM changes and answers layout questions.

Who built it:

The maintainers review each other across model families. Tobi (@tobiu) merged every pull request in this release except 31 Dependabot bumps, which merged automatically.

The one line: the backlog is clean, and the tests run only where a change can reach.

What changed:

  1. Householding (#15000, #20497): 243 open Engine issues when 13.3.1 shipped became 5 at the cut. The sweep from 2026-11-05 gave each of its 189 a premise read, without one bulk close by age.
  2. CI by reach (#20328, #20451): a pull request starts the test tiers its change can reach, superseded heads are cancelled, the lint, unit and review-admission families each run as one job, and the Brain-gated e2e tier is retired.
  3. Neural Link specs without a Bridge (#20370): single-app specs drive the page's own App Worker in hosted CI, with no Bridge and no Brain checkout.
  4. One drag sensor (#17889): a Pointer Events sensor with pointer capture replaces the mouse and touch sensors, so a drag that crosses an iframe still ends.
  5. Grid (#20379, #20380, #20359, #9486):
    • columns under shared group headers;
    • CSV export;
    • nested dataField paths in every column type;
    • the multi-body architecture for locked columns, completed.
  6. Requests that waited years: 93 issues filed before 2025 were completed, 81 of them by a pull request.

Try it: the header groups demo groups twenty regions' monthly sales into quarters, with Region and the Year total locked at either end. What you can try walks through it.

The scale: 336 pull requests merged in neomjs/neo and 424 issues closed since 13.3.1 shipped on 2026-10-23. The Full Changelog lists them.

Read for your work:

  • Upgrading from 13.3.1, if you ship the engine;
  • One drag sensor, if you build drag interactions or drive them from tests;
  • Grid, if you build data views;
  • CI by reach and From the community, if you contribute.

🧹 The householding

The operator, 2026-11-05: "We resolve 200+ tickets every week, yet inside engine and brain repos we have backlog items that are several months old and rot. We need to check if intents are still valid, even in case the code reality can have changed a lot. The benefit is getting to a clean slate which betters allows us planning new items afterwards."

Velocity stopped being the scarce part this year. Anthropic reported in October that it went from about 2,500 merged code changes a day in May to over 30,000 by September, and built its own source control to carry them; its engineers found that "very often … the role of the engineers was to tell Claude 'no'" (Anthropic). A popular tool's tracker feels the same pull: Claude Code's closed 5,139 issues in the 30 days to 2026-11-08 while 7,446 new ones arrived (GitHub). Source control scales with output; issue control takes judgment. For 13.4 we pointed our velocity at issue control, starting with our own backlog.

The charter was older than the ask. Clio (@neo-fable-clio) had filed #15000 in July: every open engine issue gets a premise read and a verdict. On 2026-11-05 it became the instrument. At 10:20Z that day the Engine had 189 open issues; 111 of them had been filed more than 180 days earlier, 94 were unassigned, and 102 were the operator's own. Thirteen days earlier, when 13.3.1 shipped, it had 243.

The rules made the count an outcome, not a target.

  • Age is not a staleness signal. Each ticket was read by the peer who knows that domain, against current source. The verdict was one of four:
    • close, citing the resolving file, pull request or ADR;
    • rewrite the body to the current architecture;
    • move to a Discussion, when the idea was valid but had no plan;
    • holds.
  • Bugs got fixed, not triaged.
  • The operator's own tickets closed like any other once he allowed it (2026-11-05), with one condition: every part of the original intent is checked first. A part that still held survived as a rewrite or a successor, never inside the close.
  • The good-first-issue set stayed open for contributors.

The domain reads ran in parallel across eight areas, from the class system to docking. Their ledgers stay in #15000's body.

The numbers come from the gate receipt (#20497, cross-checked row by row by a second maintainer).

From 2026-11-05T00:00Z to the gate at 2026-11-08T03:45Z, 260 issues closed:

  • 199 completed: 154 through a merged pull request and 45 by hand, most of those after a premise read found the work already shipped;
  • 60 not planned;
  • 1 duplicate.

104 of the 260 were filed inside the window itself, because the sweep filed its own follow-ups and closed them.

The fewest open issues since 1.0.0. We rebuilt the open count from the open and close dates of all 12,775 Engine issues. It had never been lower than 23, the count on 2019-11-17, the day 1.0.0 shipped, until this week. It passed 300 in 2023 and peaked at 542 on 2026-05-16. Issues that later moved to another repository drop out of that history, so it can only undercount the past: it shows 174 open the night before the sweep began, where 189 were measured. The 243 at 13.3.1 comes from the content-sync corpus's snapshot 13 minutes after that release. The rebuilt history agrees with it once the 18 issues the sweep later moved to other repositories are added back, and 3 that it counts open but that were closed at the time are taken out.

Tickets whose work lives in another repository moved there: Agent OS and Fleet Manager work to Institution, skill work to Skills, deployment work to the middleware. Valid ideas without a plan became Discussions, until the last night narrowed that rule. A transfer leaves no closed record here, so the 260 does not count those moves.

The last night

That night the operator walked the last tickets one by one with Vega. The gate held at 03:45Z on 2026-11-08 with 24 open, and the walk went on past it, sharpening two rules:

  • A Discussion is for an open design question that someone will work. A ticket with a clear scope and no one on it is debt in either place, so it gets built, or closed with its reason.
  • Dropping an item for lack of demand is for large items. An editor epic closed that way (#20166): work of that size is not built until someone needs it. A small item gets built.

Note

At about 04:00Z the drag-sensor rewrite (#17889) was headed out of the backlog for lack of demand: Vega proposed a Discussion, then a close. The operator: "move to discussion is non trivial. is it worth an ideation sandbox? or is the scope clear? because moving ticket DEBT into discussion DEBT is not feasible either." And of the ticket itself: "this ticket feels SMALL, something you could probably finish inside this very session." It was built that night and merged at 07:36Z; see One drag sensor.

Two user-facing defects were fixed before the tag: nested dataField paths in grid column types (#20359) and the concept ontology left in the npm package (#19093).

The last two days

Five findings already on dev surfaced on 2026-11-08, the day the cut was planned, and all five were fixed before the tag:

  • A splitter drag opened a sort session in its parent dashboard (#20588 → #20590). Maryam traced it while reviewing the new drag sensor: the splitter's drag:start bubbled up to the dashboard's sort zone, which took the splitter for a sortable item and left its container marked neo-is-dragging for at least 15 seconds after the release. The splitter's own drag listeners no longer bubble.
  • The Workstation's light theme lost its own accent in the dock's drop previews (#20591 → #20592). Since #18747 (2026-09-15) moved the app's theme sheets under :where(), the tie between the app's projection and the engine's value went to load order, and the engine's #0d9488 replaced the app's #0f766e. The visual suite's light arm caught it. The app's sheets are back at theme-root weight.
  • A grid pooling test flaked (#9160 → #20586). It now drains its two-flight mount updates before it measures.
  • Every production main.js carried the path of the machine that built it (#20600 → #20602). Webpack inlined import.meta.url at build time, so the public neomjs.com bundle shipped its builder's absolute file:// path, and the workspace-addon check tested the builder's tree instead of the page's. The main config now keeps import.meta.url a runtime value, and build-all fails a production build that ships file:/// (see Upgrading from 13.3.1).
  • The portal's news tab bar clipped its last tabs on portrait phones (#20599 → #20603). At 390×664, Pull Requests and the new Videos tab were out of reach. The portal's tab containers now use the engine's Neo.tab.plugin.Overflow: headers past the bar's height fold behind one ⋮ menu, and a tab chosen there moves the route as its header would.

Then the operator's manual test of the header groups demo blocked the cut; Grid lists what it found.

From the gate to the cut the open count went from 24 to 5: 49 more issues closed and 29 new ones were filed, most of them the last two days' own findings and follow-ups, and one closed ticket reopened until its flake was fixed. Tickets filed before the cut went into it, so the release closed everything it found.

Honest bounds:

  • "Completed" covers both work delivered in the window and premise reads that found it already shipped.
  • The open count is a snapshot at the cut, and it moves every day.
  • The Brain repository runs the same sweep separately (neomjs/neo-agent-brain#1438).

⚙️ CI by reach

The organization runs on 20 concurrent job slots, and every repository's pull requests share them. Engine CI used to start every tier on every push. #20328 measured that baseline and then cut it, leaf by leaf:

  • A scope classifier decides which tiers a change can reach. It keeps deny-lists, gives the Brain tier none, and reports its boundary (#20329, #20332). Engine pull requests then run only the e2e specs their change can reach (#20330).
  • A ruled-out tier starts no runner. On the measured corpus that saved 806 job starts (#20338, #20362).
  • A superseded head is cancelled instead of finishing (#20340, #20341).
  • One job per family instead of one per check:
  • A dev push diffs from its workflow's last completed run, so a cancelled push no longer leaves its own diff unclassified. The measured hole went from 17 pushes to 0 (#20363, #20368).
  • e2e shards are balanced by measured run time against a five-minute target per shard, replacing the split by test count. Run-to-run variance remains, so the target is not a guarantee (#20521, #20522).
  • A ruled-out components tier starts no runner either. It used to boot three runners only to report a skip; its shards now skip at job level behind one components context (#20595, #20596).
  • The repository's own guards run as one required context. Fifteen guard commands from four standalone workflows now run as one list in the shared baseline's guards job, and the baseline reports three contexts where it reported ten. A pull request starts 3 baseline jobs instead of 10, and the dev ruleset requires 7 contexts instead of 16 (#17171, #20598).
  • No Engine job checks out the Brain. The guide lint now runs inside the shared baseline at its pinned release (neo-agent-skills 1.2.0) instead of cloning the Brain's dev, so a Brain push can no longer change an Engine pull request's verdict (#20597, #20612). With that leaf, #20328 closed.

On the last night, a calendar-only fix (#20578) ran four of the tier's e2e spec files, its own two among them.

The Brain-gated e2e tier retired on 2026-11-08 (#20451, #20584). In #20328's baseline it held about two thirds of Engine CI runner-minutes. Of the 71 spec files it gated:

  • 61 moved into the engine tier, which now selects 196 of its 212 files. 54 moved with the retirement. Four followed once the off-socket adapter released a held worker session before a re-boot (#20587, #20615), and two once it could bind a second browser context by worker id (#20614, #20621). The Workstation film tour, excluded after a hosted flake, came back once its throw kept its momentum on a slow runner (#20593, #20601).
  • 10 stay local, each with its reason in LOCAL_ONLY: four test the Bridge itself, and six import Brain modules.

A pull request now starts no Brain-tier job, where it started up to ten shards before, and a full engine run is 14 jobs where the two tiers ran 18.


🔌 Neural Link specs without a Bridge

A Neural Link spec used to need the full cycle: the Brain's Bridge, a Brain checkout, and the Engine page on the other end. Discussion #18775 chose an off-socket adapter instead, which lets a single-app spec drive the page's own App Worker directly. Its epic, #20370, landed in 13.4:

The Brain keeps testing its own Bridge and tool definitions in its own repository. On 2026-11-08 #20451 brought the rest across: a local probe ran 37 of the 42 window-driving specs clean off-socket, so they joined the engine tier (#20584). Four of the six window specs that still failed off-socket (#20587) followed the same evening. The adapter's session on the App SharedWorker had kept that worker alive past its last client, so a re-booted page reached a stale worker; the fixture now releases the session before a navigation (#20615). The last two bind a second browser context by worker id, and joined once the adapter could reach a worker in another context (#20614, #20621).


🖱️ One drag sensor

Every drag in the engine starts in a main-thread sensor: dock tabs and tear-outs, splitters, grid column moves and resizes, calendar events, dialogs. Until 13.4 there were two sensors, one for mouse events and one for touch events. A drag that wandered over an iframe could go silent there, because the iframe took the moves and the release, so the drag never ended. #17889 asked for Pointer Events with pointer capture. The roadmap had deferred it, and on the last night it nearly left the backlog (see The last night). It was built that night instead, from Grace's notes of September and Euclid's intake of 2026-11-07, and merged at 07:36Z (#20585).

Neo.main.draggable.sensor.Pointer replaces Mouse and Touch:

  • one state machine hears pointerdown, pointermove, pointerup and pointercancel from mouse, touch and pen, and owns each gesture by its pointerId;
  • it captures the pointer on the document root only once a drag passes its threshold, so a press that never becomes a drag still clicks its own element, and an engaged drag ends exactly once, over an iframe or not;
  • a captured event names the document root, so the sensor adds the hit-test path as eventPath and DragDrop builds its targets from it; the drag:* payloads the App Worker receives are unchanged;
  • the engine's own drag handles (splitters, resize handles, a draggable dialog's header) declare touch-action: none, and a custom handle opts in the same way (src/main/draggable/README.md).

The proof: a new component test drags across an iframe pane. On the old sensors no move arrives inside the iframe; on the new one the moves keep streaming there and the release ends the drag once. Before the merge, the full unit suite (6,656 tests), the full component suite (581) and 136 drag, dock, splitter and sort e2e tests passed locally, and hosted CI ran every tier the change reaches.

A touch held still always arms. The arm timer re-read Date.now(), which can show the delay a millisecond short: measured, 11 of 1,500 timers of 10 ms. When it did, the arm declined and nothing re-armed it, so a perfectly still finger never started its drag, and a unit test flaked in CI. The timer now tells the sensor its delay has elapsed (#20636, #20637).

Honest bounds: touch was checked through Chrome DevTools Protocol emulation, not on a device. The operator's manual drag walk is the open post-merge check on #20585.


📊 Grid

Header groups (#20379, #20397). A columns entry with its own columns array is a group:

columns: [
    {dataField: 'name', text: 'Name'},
    {text: 'Q1', columns: [{dataField: 'jan'}, {dataField: 'feb'}, {dataField: 'mar'}]}
]

The leaf header row does not change: sorting, resizing, filters, menus, drag and aria-colindex keep working on the same flat buttons. A group row is projected above it, one part per region, so locked columns get their own group cells.

The Header Groups demo in its dark theme: group cells Q1 to Q4 over their three months each, Region locked at the start, the Year group over Total locked at the end, and the Light theme switch above the grid

The operator made "header groups finished" a second gate for this release. A verification matrix of 8 rows ran on #20379, and every defect it found was fixed before the tag (#20510, #20511, #20512, #20517). #20571 made a cancelled header drag put the body cells back.

The operator's manual test of the demo then blocked the cut, and six more changes made it before the tag:

  • A header's drag proxy kept its header. After a horizontal scroll, the proxy could show an empty slot where its header belonged. Every sort zone kept its previous drag's boundary, so the next drag's first move ran before that drag had started, and wrote screen coordinates into the header the proxy copies. A drag now ends by clearing its boundary (#20626, #20632).
  • A grouped header's proxy stays over its group, the rule its drop always followed. Past the group's edge the header waits in the edge slot, and a release keeps it there. The first fix slid the other headers back to their old order instead, and the operator's second pass rejected that (#20632).
  • A group header drags its whole group between the other top-level headers (#20627, #20630).
  • A group drag shows where its group lands. It runs through its region's column drag zone as one unit: the neighbours it passes slide aside live with their group cells, its proxy carries the group cell over its headers and their cells, and the release keeps the order the drag showed. A header drop also measures the headers only once they have rendered. Before, a flex column could take the width of headers the drag had lifted out of the flow, leaving group cells and body columns misaligned until the next resize (#20641, #20644).
  • The demo got its finish. examples/grid/headerGroups opens in the modern light theme. Above the unchanged grid it adds a title, gesture instructions and a light/dark switch. Each skin has its own palette, which the drag proxies carry too (#20635, #20638). The switch reveals the new skin in a circle that grows from the click (#20648, #20651).
  • A header drag's gap reads as a header. While a column or group drags, the slot it vacates showed the generic toolbar beneath it: near-black in neo-dark, white in the light themes. During a drag the header toolbar now takes the header-button background, in every grid. The drag proxy now ends at the last row when the rows stop above the grid's bottom (#20652, #20653).

The same demo in the modern light theme it boots in: the title Grouped column headers, the instruction to move a quarter as a group or reorder months within it, and the Dark theme switch above the full-width grid

CSV export (#20380, #20389). grid.toCsv(options) returns the store's current records as a string:

  • columns run in display order under their header text;
  • rows follow the store's filter and sort, including rows a buffered grid never rendered;
  • quoting follows RFC 4180, lines end in CRLF, dates are ISO 8601, and null is an empty field;
  • a formula guard, on by default (escapeFormulas: false turns it off), prefixes a string cell that starts with =, +, -, @, a tab or CR.

grid.exportCsv({fileName}) downloads the string. A column can override a cell through exportRenderer({record, value}).

Nested data fields (#20359, #20582). A dataField such as metrics.load already worked in plain cells, sorting and filtering. Nine column types read their value themselves and missed the path: Progress, Sparkline, Tree, Icon, IconLink, LinkedIn, CountryFlag, AnimatedCurrency and AnimatedChange. They now read through one method, Neo.grid.column.Base#getRecordValue, which uses Record#get on records and Neo.ns on the plain objects of Turbo Mode rows. AnimatedChange animates when the value at the path moves, not when a sibling member of the same object changes.

The multi-body grid, completed (#9486, #9872, #20499). Neo.grid.View now owns the up to three bodies, bodyStart, body and bodyEnd. It creates and destroys them on a runtime lock change and hands configs across them, the same way the header wrapper does. That closes the epic for zero-jitter locked columns. Its cross-window follow-ons, detaching a locked region into its own window and syncing scroll over a direct channel, are now Discussion #20579.

Smaller grid fixes:

  • tree rows announce their real expansion state (#20566, #20567);
  • a re-added tree node keeps expanding after the store sorts (#20568, #20569);
  • a header menu no longer throws over a retired trigger (#20564, #20546);
  • the themes no longer define --grid-container-header-cell-border-bottom, a token no stylesheet had read since it was added in 2022. The header's bottom border comes from --grid-container-border-color, so a theme that set the token changes nothing by dropping it (#20646, #20649).

⏳ Requests that waited years

106 issues filed before 2025 closed in this window. 93 were completed, 81 of them by a pull request. The oldest were filed in the engine's first weeks on GitHub, in November 2019:

  • Closable tabs, tab.Container (#26, #20399).
  • Docs app: routes for class tabs, so a reload restores the open class, filed the day 1.0.0 shipped (#24, #20620); tab header tooltips (#29, #20224), theme button texts (#45, #20272) and the mixins of a class (#99, #20327).
  • DateSelector: keyboard navigation and selection (#44, #20316), with arrow navigation no longer stopping at the current month (#47, #20247).

2020 followed close behind:

  • the gallery's loading spinner (#290, #20343);
  • the Helix selection model's toggleSelection (#318, #20203);
  • the calendar's 12- and 24-hour time axis (#941, #20326), plus its month view's wheel, font-size and scroll fixes (#989, #991, #992);
  • the week view's infinite scrolling (#1069, #20360).

🤝 From the community

Eight people from outside the team merged ten pull requests in this window, and for each of them it was a first pull request in this repository:

Thank you, all eight. New good first issues are filed as the team finds them: comment on an unassigned one to claim it. The claim holds for 7 days from your latest comment there (#20616, #20617).


🧰 Also in 13.4

  • Data: store pipelines compose awaited interceptors (#9553, #20487).
  • Events: a Function passed to un() or removeListener() now removes the function registered with it, by identity (#20560, #20561).
  • Calendar: pressing Escape during an event drag puts the event back instead of moving it (#20577, #20578).
  • Responsive: Neo.plugin.Responsive binds to the matching viewport on mount and rebinds when a component moves to another window (#20334).
  • Package: the npm package no longer ships the Agent OS concept ontology; it lives in the Agent OS plane (#19093, #20580).
  • Tabs: a tab chosen from Neo.tab.plugin.Overflow's menu moves the route as its header would: a plain route replaces the hash, and an editRoute header merges its fields into it (#20599, #20603).
  • Portal: the news section gains a Videos tab; each film stays a click-to-play facade until a visitor starts it, and at most one plays (#20589, #20594).
  • Dependencies: Dependabot proposes updates every day, weekends included, so a release shipped on a Saturday or Sunday no longer waits until Monday (#20606, #20607). Its first run after the change brought Playwright 1.65, with the copied Chromium launch defaults refreshed to match (#20613, #20608). A new Skills workflow release is proposed on its next run instead of three days later (#20618, #20619).
  • Rendering: with the default DOM API renderer, SVG in a component's vdom renders. svg and math subtrees are created in their own namespaces, an element inserted into a live <svg> lands as SVG, and foreignObject returns its children to HTML. The engine's Timer example draws its ring again (#20647, #20650).
  • View transitions: Neo.main.DomAccess.startViewTransition() resolves once the browser has captured the old state. Before, it resolved earlier, so a fast theme change could land in both snapshots and a circular reveal showed nothing. The Portal and Workstation reveals get the same guarantee (#20648, #20651).
  • Tooling: in an engine checkout, test runs and npm run server-start refuse an install that differs from package-lock.json, naming the drifted packages and the fix (#20642, #20643).
  • Workstation: a popup the browser refuses explains, in a hint that leaves the page usable, how to allow it (#19537, #20623).
  • Drag: Neo.draggable.container.SortZone gains two protected hooks, getPinnedItems and getDragElement. A subclass can now keep non-sortable siblings in place during a drag and clone a handle into the drag proxy without copying the drag lifecycle (#20605, #20611).
  • Forms, components and docs: the long tail of requests above, listed in the Full Changelog.

▶️ What you can try

Open the header groups demo, twenty regions' monthly sales under quarter headers:

  1. Drag the Q2 header past Q3: its three months move with it, and the headers it passes slide aside while you drag.
  2. Drag Apr to the other side of May: a month reorders within its quarter, and its proxy stays over Q2.
  3. Drag Apr on toward Jul: it waits at the edge of Q2, and a release keeps it inside its quarter.
  4. Resize a month by its header's edge: the quarter's group cell stays aligned above it.
  5. Scroll the months sideways: Region stays locked at the start, and the Year group over Total at the end.
  6. Press Dark theme: the new skin grows in a circle from the button, and the drag proxies take its palette.

On your own grid, give a columns entry its own columns array, as Grid shows above.


⬆️ Upgrading from 13.3.1

What did not change: package.json's main, exports, bin, engines and type are the same as in 13.3.1. The rest of the file changes like this:

  • three npm scripts are new: create-theme, move-files and seat:start;
  • the dompurify range moves to ^3.4.17;
  • dev tooling adds three PostCSS packages and moves neo-agent-skills from 0.1.56 to 1.2.0.

Public behaviour to check:

If your code… In 13.4 Do this
imports CountryFlag, GitHubOrgs, GitHubUser or IconLink from src/component/, or names their old className they live in src/grid/column/component/ as Neo.grid.column.component.*; the column types countryFlag, githubOrgs, githubUser, iconLink and linkedin are unchanged (#20470) update the import path or the className
uses the Neo.Responsive global it is gone; Neo.plugin.Responsive keeps its configuration per instance (#20334) use the plugin
gives a Button a route and relies on Neo.Main.editRoute editRoute defaults to false, so the button renders as an anchor and the browser follows the route (#20342) set editRoute: true to keep the old dispatch
declares a StateProvider stores entry without module, className or ntype it becomes a Neo.data.Store; before, the entry silently ended up null. An entry with an explicit className keeps its class (#20214) nothing, unless your code relied on that null
removes a listener by passing a Function to un() or removeListener() the Function is matched by identity, so only the function that was registered is removed. Before, a different function with the same name could match (#20561) keep the registered Function, or the subscription id on() returned, and remove with that. A String passed as the second argument is still read as a subscription id, as in 13.3.1
reads .neo-ai-data/concepts/ from the npm package the package no longer ships it (#20580) nothing for apps
points one of the nine column types above at a dotted dataField the path resolves as in a plain cell (#20582) nothing; values that rendered empty now show
imports src/main/draggable/sensor/Mouse.mjs or Touch.mjs, or names Neo.main.draggable.sensor.Mouse or .Touch both are gone; Neo.main.draggable.sensor.Pointer replaces them, and DragDrop exposes it as pointerSensor and getPointerSensor(), keeping mouseSensor and getMouseSensor() as deprecated aliases (#20585) use the Pointer sensor through pointerSensor or getPointerSensor()
sends raw mousedown, mousemove or mouseup through EventSimulator#dispatch, for example from the Neural Link's simulate_event each one is preceded by the pointer event a real mouse fires, with one synthetic pointerId, so a simulated gesture arms a drag as real input does; mouse listeners receive the same events as before (#20585) nothing, unless a listener counts every pointer event
handles the click a browser fires after a drag ends past the drag threshold the sensor captures the pointer on the document root, so that click lands on html and no component receives it; a press that never becomes a drag still clicks its own element (#20585) nothing, unless your code relied on the post-drag click
runs build-all for a workspace the production build fails when a .js or .mjs file under the build folder contains file:///, the path an inlined import.meta.url leaves, and the message names the file (#20602) keep import.meta.url a runtime value in your own webpack config, for example with module.parser.javascript.importMeta: false

🧭 What comes next

The roadmap for the next release is set after this cut. The backlog keeps standing bands from here on: under 25 open issues is green, 25 to 50 orange, over 50 red. A clean slate leaves room for new tickets, and the bands keep it from growing back. The five still open at the cut are content and contributor work:

  • the Grid-family examples with captions (#20500), under the epic for example overviews and the learning section (#20498);
  • a video of 100,000 grid rows that never block the page (#20185);
  • a post on the Dream Pipeline (#19058);
  • a range-field test, a good first issue a contributor holds (#19145).

📦 Full Changelog

336 pull requests merged in neomjs/neo and 424 issues closed (354 completed, 69 not planned, 1 duplicate) between 13.3.1's release at 2026-10-23T23:27:07Z and dev at 328f3f86e5. Counted at 2026-11-09T12:10Z with GitHub search: is:pr is:merged base:dev merged:>=2026-10-23T23:27:07Z and is:issue is:closed closed:>=2026-10-23T23:27:07Z (reason:completed and reason:"not planned" for the split). The counts and this list are regenerated at the cut head.

Grid and tables (42)

Docking, dashboard, drag and Workstation (40)

Forms, components and calendar (100)

CI, build and tests (101)

Docs, guides, blog and portal (40)

Other (13)

All changes delivered in 1 atomic commit: ffa130c