The same runtime, 51 of its 60 source files, and a template element in your page is a program.
Slim is not a rewrite and not a port. utils/bundle.js builds both browser bundles out of the same tree in the same run: the full one concatenates 60 runtime source files plus 9 vendor files, and Slim concatenates 51 of those same runtime files plus 2 vendor files, css.js and json5.min.js. Exactly 9 files are in the full list and absent from the Slim list, and nothing at all is in Slim that is not also in the full bundle. Those 9 are xuda_actions.js, xuda_fcm_web.js and the seven files of the signal engine: xuda_reactive.core.js, .scheduler.js, .listdiff.js, .bridge.js, xuda_runtime.render.reactive.js, .adapter.js and .pilot.js. So a Slim page is drawn by the legacy renderer alone, with no reactive engine underneath it. What you get in exchange is an authoring entry path with no build step in it: you write ordinary HTML in a template element, call xuda(), and the constructor turns every template on the page into a program keyed by its id. Two honest notes before the rest of the page. Slim is not small in bytes. The ES module the xuda.io pages import is 671,803 bytes on disk and 143,551 gzipped at level 9, and the file named xuda-runtime-slim.min.es.js is not minified: bundle.js writes the concatenated string straight out and never passes it through UglifyJS. The name describes the entry path and the 51 file source list, not the download.

Six fragments of a page that is checked into this repo.
None of this is a sketch of what the syntax could look like. Every line below is lifted from website-static/xuda.io/src/examples-html/index.html, the page the runtime is demonstrated on, and the line numbers are printed so you can open the file and diff it. Read it for the shape rather than the styling: a module script that calls the boot with one prog id, a template whose id is that prog id, xu-single-view as the root, xu-store holding local state with xu-bind writing back into it, the xu-for trio walking an array with both the index and the value named, and xu-panel mounting a second program inside the first. The xu-exp: prefix on an attribute means the value is an expression rather than a literal, and expressions here are eval: secure_eval compiles a source on its second sighting with new Function and otherwise falls back to eval in the page's own scope.
Attribute handlers in the directive registry
Structural tags an author is meant to write
Runtime source files in the Slim list, of 60
Assertions passing in runtime/test, 0 failures
One constructor, and three mutually exclusive ways a Slim page finds its programs.
xuda() takes an element, an options object and a callback in any argument order, detected by type rather than by position, and under the Slim bundle a missing element falls back to the string body with a warning instead of aborting the boot. What happens next is decided by exactly one question, asked once: are there template elements on the page. The three answers below do not blend. The first rewrites the page, the second leaves it alone, and the third is the failure mode, and it is worth knowing which one you are in before you start debugging a blank screen.
Nine files is the whole difference, and each one is something a Slim page cannot do.
Both bundles are built from the same tree by the same script, utils/bundle.js. Slim concatenates 51 runtime source files plus 2 vendor files. The full browser bundle concatenates 60 runtime files plus 9 vendor files. Diffing the two lists gives exactly 9 names in the full list and not in the Slim list, and nothing in Slim that is not also in the full bundle. Slim is a subtraction, never a fork, which is why an expression that works in one works in the other. What follows is those nine names and the four early returns that go with them.
Slim | Full browser bundle | |
|---|---|---|
| Runtime source files | 51, concatenated at utils/bundle.js:243 and written straight out as a string. | 60, at utils/bundle.js:62. The extra nine are xuda_actions.js, xuda_fcm_web.js and the seven reactive engine files. |
| Vendor files | 2: css.js and json5.min.js. | 9: those two plus pouchdb.min.js, pouchdb.find.min.js, client.min.js, fingerprint2.min.js, socket.io.min.js, peerjs.min.js and clona-2.0.6.min.js. |
| Signal engine | Not shipped. xuda_reactive.core.js, .scheduler.js, .listdiff.js, .bridge.js and the three render.reactive files are all absent. A Slim page is drawn by the legacy renderer alone. | Shipped: the reactive renderer, its scheduler, its list diff, and the pilot that decides which renderer a program gets. |
| Database adapter | Never loaded. Boot returns at runtime/js/index.js:407, before the project loader and the adapter at :423 to :429. func.db is never assigned and APP_OBJ[app_id] stays an empty object. | xuda-db-adapter-module.mjs is imported, func.db is assigned, and init_db_replication starts on the app id. |
| Framework plugin | init_ui_framework returns on its first line when glb.SLIM_BUNDLE is set, at xuda_UI.utils.js:521. The Tailwind and Ionic framework plugins never initialize. | The framework plugin for the program is resolved and initialized before the tree is walked. |
| Background plugins | register_run_background_plugins returns the same way at xuda_main.js:199, so nothing runs in the background. | Walks app_plugins_purchased and starts every entry carrying installed and run_in_background. |
| Program source | The DOM. xuda() runs document.querySelectorAll('template') at index.js:225, keys each program by its id or template_<index>, takes the first id as prog_id, then empties the root element. A missing id ends at console.error, xuda_utils.js:105 to 144. | _session.project_data.programs first, and only when SLIM_BUNDLE is unset does the lookup fall through to xuda-progs-loader-module. |
Twenty attribute handlers, and an order that is two arrays long.
The directive registry is a plain object of 20 handlers, from xu-attrs at line 68 to xu-viewport at line 234 of xuda_runtime.browser.handlers.factory.common.js. One further xu-exp handler is composed on top, and tag-scoped handlers are added for xu-panel and xu-teleport. That is the whole vocabulary. There is no compiler step and no template language: the attribute is read off the element and the value is evaluated in the page.
- The 20, in source order: xu-attrs, xu-ref, xu-bind, xu-render, xu-show, xu-content, xu-text, xu-html, xu-for, xu-for-key, xu-for-val, xu-class, xu-on, xu-script, xu-style-global, xu-style, xu-cdn, xu-ui-plugin, xu-store, xu-viewport. Six structural tags are yours to write too: xu-template, xu-single-view, xu-multi-view, xu-panel, xu-teleport, xu-slot. Two more, xu-nav and xu-modal-controller, are internal, and the studio checker guards against authoring them.
- xu-if, xu-else-if and xu-else are native to the reactive engine only. For the legacy renderer, which is the only renderer Slim has, each branch is compiled into an equivalent xu-exp:xu-render at xuda_common.js:763 to 782, and the authored xu-if attributes are deliberately left in place so the source you wrote stays readable in the DOM.
- xu-render and xu-if remove the element. A false gate leaves a bare xurender element behind as the anchor to grow back from, at xuda_runtime.browser.dom.js:554. xu-show keeps the node mounted and toggles visibility only. Choosing between them is choosing whether a hidden subtree costs you DOM.
- Expressions are eval, and that is not a figure of speech. secure_eval compiles a source on its second sighting with new Function('xu','func','glb','SESSION_ID','SESSION_OBJ','dsSessionP',...) at xuda_expression.js:1116, and otherwise falls back to eval(val) in the page scope at :1134. A Content Security Policy without unsafe-eval cannot run a single xu-exp: attribute.
- run_xu_beforexu-cdn, xu-style, xu-render, xu-for-key, xu-for-val, in that order, from xuda_GLB.js:175 to 181. The gate and the loop keys are settled before anything reads them.
- The bodyEvery remaining handler, in the order the attributes appear on the element. xu-for, xu-content, xu-show, xu-on and xu-store all run here.
- run_xu_afterxu-bind, xu-class, xu-script, xu-ui-plugin, xu-ref, from xuda_GLB.js:184. Binding and scripts see a finished element, and xu-ref is handed out last.
- The xu-exp passxu-exp: is not a 21st handler. It is composed over the registry in factory.js and marks the value as an expression, so xu-exp:xu-render still occupies the xu-render slot inside run_xu_before rather than a slot of its own.
Eight shorthands, one rewrite, and no second event system underneath.
Eight event abbreviations are declared in one array, glb.attr_abbreviations_arr at xuda_GLB.js:185. None of them is a handler. At normalize time, xuda_runtime.browser.attributes.policy.js:42 to 56 checks whether the attribute name is in that array, writes xu-on:<event> in its place carrying a custom handler and a workflow of exactly one update step, then deletes the original attribute. xu-click and the long form xu-on:click reach identical code by the same route, which is why there is nothing to learn about the shorthands beyond the list of eight.
Import the module, write a template, call the constructor.
No build step, no bundler config, no install. The xuda.io examples pages do these three things and nothing else, which is why they are the honest reference rather than a tutorial written after the fact.
- 01
Import the module
The ES module the xuda.io pages import is website-static/xuda.io/src/assets/xuda-runtime-slim-es.js. It is 671,803 bytes on disk and 143,551 gzipped at level 9, and the source tree copy and the built dist copy are byte identical, md5 e950abcb5372188891ed775c62d55be4. One caution about the name: the sibling file called xuda-runtime-slim.min.es.js is not minified. bundle.js concatenates the sources into a string and writes it out at :312 without passing it through UglifyJS, and only the later vite plus terser step in the npm package minifies. Slim describes the 51 file source list and the authoring entry path, not the download.
- 02
Give the page a template with an id
A plain HTML template element is aliased onto xu-single-view in every renderer, at xuda_runtime.browser.nodes.js:410 to 432. That alias is load bearing: a real template element is inert by spec, so an unaliased path would call createElement('template') and the program would render nothing. The examples page opens its own at website-static/xuda.io/src/examples-html/index.html:79, template id xuda_slim_html_examples, with xu-single-view on the next line. HTML authoring costs one network fetch that JSON authoring does not, because the template scan dynamically imports xuda-cli-plugin-html-parser-module.esm.mjs, 748 lines, at index.js:227 to turn markup into a node tree.
- 03
Call the constructor with that id
website-static/xuda.io/src/assets/site.js:812 calls new Xuda(document.getElementById(mountId) || document.body, {prog_id, ...}). xuda() takes an element, an options object and a callback in any argument order, detected by type rather than position, at runtime/js/index.js:41 to 60, and under Slim a missing element falls back to 'body' with a warning instead of aborting the boot. Then the markup does the rest: xu-store with xu-bind at index.html:194 to 197, the xu-for trio at 361 to 363, an xu-panel at 728. One caveat before you copy that file: the ES module checked into website-static is older than runtime/js. Grep finds no xu-teleport, no xu-attrs, no xu-if and no DS_LOADING in it, while the examples page authors xu-teleport under a Live Preview heading.
Nine things a Slim page does not do, each with the line where you can read the refusal.
- Slim boots and returns at runtime/js/index.js line 407, before the project loader at 423 and the db adapter at 425. func.db is never assigned and APP_OBJ[app_id] is left as the empty object set at line 400, so a drop-in page has no PouchDB store, no replication and no plugin registry.
- xu-ui-plugin and xu-widget do nothing in a plain Slim page. app_plugins_purchased is never populated, so the first gate in run_ui_plugin, plugin?.installed at runtime/js/xuda_common.js line 2105, is false on every call. The xuda.io examples page labels its own xu-ui-plugin card Reference-only, at examples-html/index.html line 674.
- It is not a sandbox. secure_eval compiles a source on its second sighting with new Function at runtime/js/xuda_expression.js line 1116 and otherwise falls back to eval(val) at line 1134, and a UI plugin arrives by dynamic import into the page own realm holding the raw DOM node. A Content Security Policy without unsafe-eval cannot run a single xu-exp: attribute.
- The two framework integrations are switched off rather than degraded. init_ui_framework returns on the first line of its body when glb.SLIM_BUNDLE is set, at runtime/js/xuda_UI.utils.js line 521, and register_run_background_plugins does the same at runtime/js/xuda_main.js line 199. Tailwind and Ionic never initialize under Slim.
- The signal engine is not in the file list. The seven reactive files, xuda_reactive.core.js, .scheduler.js, .listdiff.js and .bridge.js plus xuda_runtime.render.reactive.js, .adapter.js and .pilot.js, sit in the full list at utils/bundle.js lines 131 to 138 and in no Slim list. A Slim page is drawn by the legacy renderer alone.
- func.action is undefined, because xuda_actions.js is one of the nine files the Slim list omits. The invoke_action workflow step at runtime/js/xuda_events.js line 920 and the realtime act_refresh path at runtime/js/xuda_runtime.browser.realtime.js line 107 both call it. Only the second one sits inside a catch.
- The ES module checked into website-static/xuda.io is older than runtime/js. Grep it for xu-teleport, xu-attrs, xu-if, DS_LOADING or normalize_doc_for_runtime and every one of the five returns zero hits, while the examples page authors xu-teleport under a Live Preview heading.
- Slim is not small in bytes. website-static/xuda.io/src/assets/xuda-runtime-slim-es.js is 671,803 bytes on disk and 143,551 gzipped at level 9. The name describes the authoring entry path and the 51-file source list, not the download, and no framework size comparison is sourced anywhere in this repo.
- Two of the 29 test suites cannot run from a clean checkout. allprogs_sweep.mjs and realprog_dryrun.mjs both read runtime/test/fixtures/progs, and runtime/test/fixtures holds nothing but a .gitignore, so both throw ENOENT before asserting anything.
The walkthrough, after the mechanism rather than in place of it.
Every claim above was read out of the tree on 2026-08-19 and carries the file and the line it came from. A recording carries neither, and a recording ages. So where the video and this page disagree, the page wins, and where you doubt the page, open the file it names. Watch this for the shape of a drop-in, then read the callout again for what the drop-in will not do.
The six questions the file list raises
Answered against the tree as it stands on 2026-08-19, each with the file and line you can check it against.
Two doors, one project_data shape.
The drop-in door is a script tag and a template element. Under Slim, xuda() runs document.querySelectorAll("template"), turns each result into a program keyed by its id or by template_0, template_1 and so on when it has none, takes the first id as prog_id and then empties the root element innerHTML, at runtime/js/index.js lines 225 to 252. It also takes element, options and callback in any argument order, detected by type rather than position, and a missing element falls back to body with a warning instead of aborting, at lines 41 to 60. HTML authoring costs one network fetch that JSON authoring does not: a dynamic import of xuda-cli-plugin-html-parser-module.esm.mjs at line 227, 748 lines, to turn your markup into a node tree. The dashboard door skips the scan and hands the runtime the same programs map directly, which is why the attributes you learn here transfer intact. Take the first if you want a page. Take the second if you want the database, the replication and the 62 plugins that the Slim boot returns before reaching.