Skip to content

Changelog

The JOPS website is live: jops.ui-db.com, with guides for every platform, concepts, the full API reference and examples. The library is identical to 1.1.9.

  • The npm package’s homepage is now https://jops.ui-db.com, and the README links to it at the top.
  • JOPS WordPress plugin: “Visit plugin site” opens the WordPress guide on the website, and the author link points to https://ui-db.com.

Documentation only. The library is identical to 1.1.8.

  • Store is described as an app-wide shared data store instead of a state container, since it stores data and has no reactivity: README heading “Store — Shared Data Store”, AI guide heading “Shared data and events: Store and EventBus”, and the Store description in the type definitions.

Fixes for npm install in projects created with npx jops-core init, more complete type definitions, and documentation corrections. The library only adds the Element export; its behaviour is unchanged.

  • npm install failed where nobody can answer a question (CI, Docker) in web, Cordova and Capacitor projects created by npx jops-core init: their "postinstall": "npx jops-core init" stopped at the template question and exited with an error. When input ends without an answer, init now uses 5 (None), which only refreshes the library and adds what is missing. A piped answer (echo 2 | npx jops-core init) still works.
  • init now adds "postinstall": "jops-core update", which only copies the library. Existing projects keep their script; changing it to jops-core update avoids the question.
  • Hello World template: the stylesheet was loaded twice (by setStyle() and by a <link> in the layout); the layout’s <link> is removed. Media.js is referenced with a root-based path like the other views.
  • Element (returned by get() and find(id)) is exported, so it can be used in your own type annotations.
  • View declares the optional lifecycle hooks onLayout(), onResume(), onPause() and onBackPressed(), so editors suggest and document them in subclasses.
  • Internal members (names starting with _) are no longer part of the type definitions.
  • hide(), show(), destroy() and inflate() describe their behaviour for layouts with several top-level elements.
  • README: setStyle() has no effect in any hosted mode (Next.js, WordPress, mount()), not only in Next.js; the default entry point when data-app is missing (./src/App.js); the Electron exception to the file:// note; how the layout cache handles views loading the same file at the same moment; where to read route params; the missing import { Router } in the Capacitor back-button example; the pin example uses the current version.
  • AI guide: written against 1.1.8; the same setStyle() and postinstall corrections.

Bug fix in addSubView(). Calls that worked before find the same container.

  • addSubView(containerId, view) now finds its container the same way as get(): in every top-level element of the view’s layout, in order, and the container may be a top-level element itself. Before, only the inside of the first top-level element was searched, so a container in a later top-level element (e.g. <h2>…</h2><table><tbody id="rows">) or a top-level container logged “no element #… in the view” and nothing was added.
  • addSubView() with a container id that is not a valid CSS identifier (for example one starting with a digit) no longer throws a SyntaxError.
  • README, AI guide and type definitions: the addSubView() container lookup; insertSubView() inserts among the children of the first top-level element. The addSubView() parameter is named containerId in the type definitions, as in the README.

Electron support in the CLI: a new Electron template, and init / update recognise Electron projects. The library is unchanged from 1.1.5.

  • npx jops-core init template 4. Electron JOPS Desktop App: creates an Electron Forge project in the current folder (main process, preload bridge with two sample IPC calls, a JOPS SPA with a sidebar and <Router jops screens>, a Content-Security-Policy), vendors the library to src/lib/ and runs npm install. An existing package.json is merged (its entries are kept; jops-core goes to devDependencies). “None” is now choice 5.
  • init in an existing Electron project (Electron Forge’s default template, or any project with electron in package.json that loads a plain HTML page): vendors the library into the page’s folder (src/lib/), replaces Forge’s sample page (kept as index.forge.html) or patches your own with the import map, the module script and a Content-Security-Policy, copies templates 1 and 2 next to index.html with page-relative paths, moves jops-core to devDependencies and adds a jops:update script. Forge’s vite and webpack templates are refused, since they bundle the page. --electron forces the project type.
  • npx jops-core update refreshes the library in an Electron project; npx jops-core build points to npm run make there.
  • README: new section “Desktop Apps (Electron)”: the template, adding JOPS to an existing Electron project, paths under file:// (page-relative, no leading ./ or /), calling the main process through the preload bridge, events from the main process, the Content-Security-Policy, conventions. CLI and init menu updated.
  • AI guide: Electron section, and the path rule per setup gets an Electron line.

Documentation only, no code changes.

  • README and AI guide, Router screens mode: how to style screens and the Router container through the .jops-screen and .jops-router classes (e.g. padding inside every screen), and which properties the Router sets inline and therefore take precedence over app CSS (position, inset, overflow and background on screens; position and overflow on the container). Use --jops-screen-bg for the screen background.

Maintenance release: fixes for Router transition glitches in app-like layouts, and documentation corrections. A Router without the new screens attribute behaves exactly as in 1.1.3.

  • Router screens mode, an opt-in fix for layouts where the Router fills part of a fixed app window (desktop, hybrid, a fixed toolbar). With <Router jops screens>, the Router renders into its own container (<div class="jops-router">, sized by the app’s CSS) instead of the tag’s parent element. Every route and stacked view is a full-size screen in it that scrolls on its own and has an opaque background (--jops-screen-bg, default Canvas). This fixes, for such layouts: views shrinking during the slide and fade transitions, the previous view showing through the new one, the shared scroll position (Back now returns to where a list was scrolled), the transitions overwriting the parent element’s inline styles, and views with several top-level elements switching without animation. A new view covers the current one, which stays alive underneath.
  • README and AI guide: new “Screens mode” section in the Router docs; removed the outdated rule that a route’s own layout (and the layout of a view added with addSubView()) is not scanned for declared views, which no longer applies since 1.1.3.

Bug fixes in rendering declared views (layouts with several top-level elements, top-level jops tags, views in Router routes, nested tags, views added with addSubView(), views removed while loading) and in the Router hiding routes. No API changes.

  • Views declared in a layout with more than one top-level element are now rendered in every top-level element. Before, only the declared views (<X jops …> tags) inside the first top-level element were rendered; tags in the second and later elements stayed in the page as unrendered elements.
  • A declared view (<X jops …>) or <Router jops> that is itself a top-level element of a layout is now rendered, e.g. a layout that starts with <Header jops …></Header> followed by other elements. Before, it was rendered only when wrapped in another element. The view that owns the layout keeps the rendered elements as its own, so get(), find(), hide() / show() and removing the view also cover them.
  • Views declared in the layout of a Router route are now rendered. Before, a route rendered only the plain HTML of its layout, and its <X jops …> tags (and those of the views inside it) stayed in the page as unrendered elements, with no error.
  • The Router now hides inactive routes and covered stacked views even when the app’s CSS sets their display (e.g. .stats { display: grid }). It used only the hidden attribute, which such CSS overrides, so the previous route stayed visible next to the new one.
  • A declared view nested inside another declared view’s tag (<Outer jops><Inner jops></Inner></Outer>) is now created once and belongs to the outer view. Before, both the outer view and the view containing it could create it, depending on load timing, so it was rendered twice or owned by the wrong view.
  • A view added with addSubView() or insertSubView() now renders the views declared in its layout (<X jops …> tags), before its onLayout(), as a declared view does. Before, only its plain HTML was shown and those tags stayed unrendered.
  • A view removed while its declared views are still loading now stops them: the ones that finish loading afterwards are destroyed instead of running onLayout() / onResume() and staying attached to the removed view.

WordPress integration: a JOPS SPA can now run inside a WordPress page, next to the theme and the page’s other content, through the new JOPS WordPress plugin. See the new README section “WordPress Integration”.

  • New JOPS WordPress plugin with the [jops] shortcode: [jops app="/jops-spa/src/App.js" id="jops-spa-root"] embeds a JOPS app in a page, e.g. with Elementor’s Shortcode widget or the block editor’s Shortcode block. Attributes: app, id, class, lib. The plugin includes the jops-core library. One app per page; in the Elementor editor the widget shows a placeholder, and the app runs on the published page.
  • Install without npm: jops-wordpress.zip is attached to every GitHub release (always the newest: https://github.com/uidb-dev/jops-core/releases/latest/download/jops-wordpress.zip); install it with wp-admin → Plugins → Add New Plugin → Upload Plugin. To update, upload the newer zip and choose “Replace current with uploaded”.
  • npx jops-core init --wp installs the plugin with npm: into plugins/jops/ of a wp-env project (and adds it to .wp-env.json), otherwise into the plugins folder of the WordPress installation found in the current folder or above it (standard layout or Bedrock). --wp-root <path> points to WordPress instead of searching.
  • npx jops-core update also refreshes an installed JOPS WordPress plugin and its library.
  • View.removeSubViews(condition?): removes and destroys every subview, or only those matching condition (the others keep their order). Not available on a Router (logs a warning).
  • The jops:ready event now fires after jops-core has fully initialised, so calling mount() from the listener no longer fails (it threw Cannot set properties of undefined (setting 'pushState') when the listener was registered before the library loaded).

Documentation only; no library changes.

  • README: hosted mode (data-host, mount() / unmount()) moved out of the Next.js section into its own section, “Embedding JOPS in Other Pages (Hosted Mode)”, for plain HTML pages and other server frameworks. The example now loads the library from jsDelivr, and the section explains how paths resolve with the CDN (relative to the hosting page) and with a vendored library (root-absolute). Inside a Next.js page, always use JopsApp.
  • README: data-root (render a standalone app into an element instead of <body>) is now documented in the CDN installation section.

Next.js integration: a JOPS SPA can now run inside a page of a Next.js (App Router) app, next to React, with Next.js Route Handlers as its backend. See the new README section “Next.js Integration”. This release also adds new View and EventBus APIs and fixes found while building a full app on JOPS inside Next.js.

  • New JopsApp React component, imported from jops-core/nextjs: <JopsApp app="/my-app/App.js" /> loads the library once per page, adds the "jops-core" import map and mounts the app into its own <div>. Props: app, id, className, lib. react and next are optional peer dependencies; plain JOPS apps don’t need them.
  • Hosted mode: load jops-core with data-host and nothing starts automatically; window.JOPS.mount({ app, root }) starts an app inside any element and window.JOPS.unmount() ends it. mount and unmount are also exported by the module.
  • Leaving the host page (a link or Back past the app’s first entry) ends the app completely: Router, views, container content, Store, EventBus and JOPS’s history hooks are all removed. Coming back launches it again and restores the screen from the hash. Next.js links to the app’s own page are handled by the running app, and Next.js history state is kept in every entry JOPS writes.
  • setStyle() has no effect inside Next.js: the host owns the CSS (Tailwind CSS in Next.js also scans the JOPS files in public/).
  • npx jops-core init has a new template, 3. Next.js JOPS Embedded SPA: a Next.js project with a React page, a page hosting a JOPS SPA (mock login and a quotes list) and a Route Handler backend. It is written into the current folder and merged into an existing package.json.
  • npx jops-core update detects Next.js projects and copies the library to public/lib/.
  • Standalone apps can render into an element instead of <body>: data-root="<id>" on the jops-core script tag.
  • onPause() lifecycle hook: called when another view comes to the front while this one stays alive (another route shown, a stacked view opened with navigate(), replace() to another view). Pairs with onResume().
  • Tag props: attributes on a declared jops tag reach the view as read-only this.props (camelCase names, string values), e.g. <Card jops src="/src/Card.js" title="Hi" item-id="7"> → { title: "Hi", itemId: "7" }. Views created in code can pass props as the second constructor argument: new View(layout, props).
  • Layouts may have several top-level elements. get(id) searches all of them; this.domNodes holds them all (this.domNode stays the first).
  • find(id) returns an Element for every element with that id; find(predicate) is unchanged.
  • EventBus.subscribe(owner, event, callback): the subscription is removed automatically when the owner (usually a view) is destroyed. Also new: EventBus.unsubscribeAll(owner) and EventBus.clear(). The global subscribe(event, callback) form is unchanged.
  • Element.from(element, id).
  • The root view now gets the full lifecycle (onLayout(), onResume(), event binding) and get() works on it; it used to get none, so onclick handlers in its own layout failed.
  • Declared views keep their layout order, so the first route in <Router> is reliably the default route (it used to depend on which view file loaded first).
  • Router routes are resumed only when shown: at startup only the selected route gets onResume(), once (hidden routes got it too, and the selected one twice).
  • A view opened with navigate() gets onResume() once (it was called twice).
  • An empty hash after startup (href="#", Back to an entry without a hash) shows the default route instead of being ignored.
  • Router.replace() keeps the existing history.state.
  • loadLayout() loads once per view: overriding loadLayout() no longer fetches each layout twice, and new View("/layout.html") + addSubView() now works (it threw a TypeError).
  • setStyle() with inline CSS containing line breaks or quotes no longer throws a SyntaxError.
  • Element.set(), attr() and value() never throw: a wrong id logs a warning with the id, and calls on a destroyed view (e.g. after an async request) do nothing.
  • addSubView() / insertSubView() with a destroyed view, a missing container or a layout that failed to load log a warning and add nothing (they threw, or left the view half-added).
  • View.destroy() removes the view’s owned EventBus subscriptions; destroying the root view empties its container instead of removing it.
  • npx jops-core init: “None” moved from choice 3 to choice 4. Scripts that pipe 3 into init now get the Next.js template.
  • find("some-id") with a string used to throw; it now returns an array of Elements.
  • New npm keywords: nextjs, next-js, next.js, ssr.
  • CLI: init, update and build now tell you when a newer jops-core is on npm, with the exact upgrade commands. If you run update before npm i jops-core@latest, it explains that it copied the old version. The check takes at most 1.5 s, is silent when offline, and is skipped when CI is set. Turn it off with --no-update-check or JOPS_NO_UPDATE_CHECK=1.
  • CLI: build warns when lib/jops-core.min.js doesn’t match the installed package, i.e. npx jops-core update was forgotten after an upgrade.
  • Added npm keywords for hybrid mobile discoverability: cordova, capacitor, ionic, mobile, android, ios, hybrid-app, hybrid-mobile, phonegap.

Hybrid mobile support — JOPS apps now run in Apache Cordova and Capacitor (iOS + Android), tested with cordova-ios 8, cordova-android 15 and Capacitor 8. See the new README section “Hybrid Mobile Apps (Cordova, Ionic & Capacitor)”.

  • Android back button: in Cordova and Capacitor (with @capacitor/app), the hardware/gesture back button now navigates the Router (Router.back()) and closes the app on the first screen. Required on Android 16 (targetSdk 36), where back otherwise closes the app immediately. Activates only on deviceready, so regular browsers are unaffected; opt out with data-native-back="off" on the jops-core script tag. In Capacitor without @capacitor/app, a console warning explains what to install.
  • npx jops-core init detects Cordova (config.xml) and Capacitor (capacitor.config.*) projects — run it from the project root (or from inside www/ / webDir) and it writes the app into the folder the shell serves. In hybrid projects it also:
    • adds viewport-fit=cover to index.html;
    • Cordova: allows the inline import map and Thread workers in the Content-Security-Policy ('unsafe-inline', blob:), adds cordova.js, backs up Cordova’s sample page as index.cordova.html, and sets the iOS custom scheme (app://localhost) in config.xml — existing values are never changed;
    • keeps .vscode/ and package.json scripts in the project root, skips the build script, and npx jops-core build is not used there;
    • --cordova, --capacitor and --web override the detection. Plain web projects get exactly the same output as before.
  • Router: new canGoBack() method.
  • Router fix: onBackPressed() is no longer called on forward navigation (tapping an href="#route" link fired it in every browser). It now runs only when the user actually navigates back. History entries carry a position (history.state.jopsIdx), so back and forward are told apart, including after a reload.
  • Router: tapping a flat-route link while navigate() views are stacked now closes the stack (previously it played a back animation), and back skips the history entries of those closed views.
  • Hello World template: header and footer are padded with env(safe-area-inset-*) for notches, the Dynamic Island and the home indicator (no change in regular browsers).
  • Router: deep links keep their query string on first load (index.html#form?tab=2), so getParams() returns the params immediately.
  • HTML file layouts: loadLayout() and the layout= attribute now accept .html files in addition to .js modules. The framework fetches and caches the file for the page lifetime — identical dedup behaviour to JS module layouts.
  • Path normalisation: isCDN auto-detected at startup; normalizePath() ensures correct relative/absolute paths for CDN vs npm installs, applied at all dynamic import and fetch sites.
  • Router: all route subviews are now initialised as hidden, eliminating the brief flash of non-active views on first render.
  • Router: initial navigation now honours a pre-existing URL hash — loading index.html#media routes directly to /media instead of always defaulting to the first route.
  • Path normalisation: isCDN flag auto-detected at startup; normalizePath() ensures ./-prefixed paths for CDN and /-prefixed paths for npm/lib installs, applied at all three dynamic import sites.
  • README: added “JOPS Demos” section with a link to the Hello World live demo.
  • New attribute syntax: use jops and jops-event-bind as plain boolean attributes instead of type="jops" / type="jops-event-bind". Old syntax still works — fully backwards compatible.
  • README: moved “Updating the library” section to follow the npm installation section for better documentation flow.
  • Fixed Thread.start(): class method shorthand (run() {}) was serialized into an invalid worker blob. Now correctly wrapped as a function expression.
  • Added Thread class: extend and override run(params) and onMessage(event) to run work on a Web Worker. start(params) spawns the worker; terminate() stops it.
  • Added npx jops-core update command: copies the latest lib/jops-core.min.js from node_modules into the project’s lib/ folder without re-running full init.
  • README: Thread class reference, ThreadPool future note, updating workflow docs.
  • Fixed CDN loading: bootstrap now uses document.querySelector("script[data-app]") instead of document.currentScript (which is always null in a module script).
  • Fixed CDN loading: all dynamic import() calls for user-supplied paths now resolve against document.baseURI via new URL(path, document.baseURI).href, so layouts and view modules no longer 404 when the library is served from a different origin (e.g. jsDelivr).
  • README CDN snippet updated: added type="module" to the script tag, changed data-app to a relative path (./src/App.js), added HTTP server note.
  • README: added npm i -D jops-core note for CDN users to get editor autocomplete via lib/jops-core.d.ts.
  • Added TypeScript declarations (lib/jops-core.d.ts) with JSDoc for all public exports: View, Router, Store, EventBus, layout, raw. Added "types" field to package.json.
  • Updated CDN examples to jsDelivr (@latest) in README and landing page.
  • MIT license.
  • Patch release.
  • Patch release.
  • Initial public release: View, Router, Store, EventBus, CLI (npx jops-core init, npx jops-core build), empty and hello-world templates.