- 1.38.2: Cleanup with no visible change. The service worker no longer imports localForage: it imported the localforage script handle (registered by LH Localforage, when active), but no service worker code has used it. The unused bundled copy, assets/scripts/localforage.min.js, has been removed; it was never registered. Plugins that do need a script in the worker can still add its handle with the lh_web_application_import_scripts_handle_filter filter.
- 1.38.1: Code quality fixes, with no change in behaviour. The install sheet’s built-in styles are now added through WordPress’s style API, as an inline style on the lh_web_application-install handle printed in the footer with the sheet, instead of being echoed as a raw style tag; they can still be changed with the lh_web_application_install_sheet_css filter. The offline page asks for its GMT date with the documented boolean argument, the script tag attribute filter asks for script tags in WP_HTML_Tag_Processor’s documented array form, and a redundant empty check on the manifest shortcuts was removed.
- 1.38.0: New Install the App block (lh-web-application/install-button) for asking people to install the app where it makes sense on a site, such as a members’ page, a registration confirmation or a newsletter landing page. It shows a button styled by the theme that opens the install sheet, and its settings are the button text (default Install the app), alignment, a trigger name reported to analytics as detail.trigger on the sheet’s events (default block; lower case letters, numbers, dashes and underscores), and Hide once installed (on by default), which hides the button in a browser that has installed the app. The button is always hidden inside the installed app itself, where it could do nothing, and when printing. The block is rendered on the server and its small stylesheet loads only on pages that use it. The install script now sets the body class lh_web_application-is_installed in a browser that has installed the app, and clears it again if the browser later offers to install it, meaning the app was removed.
- 1.37.0: Replaces the pwa-install web component with LH Web Application’s own install sheet. The sheet slides up from the bottom of the page, can be closed with its close button, the Escape key or a tap on the shaded background, and shows the right steps for the visitor’s device: the browser’s own Install button where one is offered (Chrome, Edge, Samsung Internet), Share then Add to Home Screen on iPhone and iPad, the browser menu on other Android browsers, and File then Add to Dock in Safari on a Mac. It uses the app’s short name and iOS icon, and its styles are scoped to the sheet so a theme’s button and heading rules cannot leak in. It is opened by the Install App menu item as before, and now also by any element with a data-lh-web-application-install attribute. New: the sheet can offer itself once to visitors on a phone from their third visit. A new Install prompt setting under Settings > Web Application chooses who is offered it (signed-in members of the site, the default; everyone; or no one), and a Why install setting sets the line explaining what the app gives people. Closing the sheet keeps it away for 30 days, on that device and, for signed-in members, on every device; installing the app keeps it away for good. The visit count and snooze length can be changed with the lh_web_application_install_prompt_visits and lh_web_application_install_prompt_snooze_days filters, and the sheet’s wording, styles and audience with lh_web_application_install_sheet_html, lh_web_application_install_sheet_css, lh_web_application_install_reason and lh_web_application_install_prompt_audience. The sheet fires lh_web_application:install_shown, install_dismissed, install_accepted and app_installed events on window for analytics plugins to record. The install script is now small, deferred and loaded on every front-end page instead of loading the third-party bundle.
- 1.36.2: The lh_web_application-navigating body class, used by themes to show a page loading bar, is now only added in the installed web app (standalone or fullscreen display mode, or an iOS home screen app). Ordinary browser tabs already show their own loading indicator, so the bar no longer appears there.
- 1.36.1: The floating Back button now keeps its own look on every theme. Its built-in styles had zero specificity, so a theme’s general button rules won. On LH 2015 that gave a square dark grey button, padding that shrank the arrow to a speck, and a grey focus state that stuck after a tap. A site’s –lh-web-application-back-* colours were ignored too. The styles now use the button’s id, reset what themes commonly set on buttons (padding, font, case, appearance, hover and focus backgrounds), and draw a slightly larger, bolder arrow. Keyboard focus shows a ring. Themes can still restyle it through the custom properties or a rule naming #lh_web_application-back.
- 1.36.0: Adds a floating Back button to installed web apps on iPhone and iPad, which have no browser back button. It sits in the bottom left corner, clear of the home indicator and the notch, and shows only while there is a page in the app’s history to go back to. It slides away while you scroll down or type and returns when you scroll up. It is a labelled button (“Back”, translatable) at least 48px across. Android installed apps are left alone because they have a system back button. The new lh_web_application_back_button filter chooses where it appears: ios (the default), standalone (any installed app) or none. Themes can restyle #lh_web_application-back or the –lh-web-application-back-background, -color, -border and -size custom properties. The body gets lh_web_application-has_back while the button shows.
- 1.35.2: The lh_web_application-navigating body class is now added the moment a link to another page on the site is tapped or a form is submitted, as well as from the Navigation API. Before, it waited for the browser’s navigate event, so on iPhone a theme’s loading indicator could miss being drawn before the old page was replaced. Links to other sites, new windows, downloads and same-page anchors are left alone, as are clicks and submits another script has cancelled. Browsers without the Navigation API now get the class too. A 30-second safety timeout clears it if no new page arrives, for example when a link turns out to be a file download.
- 1.35.1: Pages built from block templates now carry a single viewport tag. Core printed its own tag (without viewport-fit=cover) ahead of the plugin’s, so the page had two. iOS could then ignore viewport-fit=cover, leave env(safe-area-inset-*) at 0 and let content and fixed elements, such as a theme’s lh_web_application-navigating loading bar, sit hidden under the status bar of an installed app in portrait. The plugin now removes core’s tag and prints only its own.
- 1.35.0: Adds a body class for page-loading indicators. While the next page is loading, the body gets the class lh_web_application-navigating, removed again when the navigation finishes or fails, after an in-page anchor, and when a page is restored with the back button. An installed web app has no browser loading bar, so themes can use the class to show their own, for example scoped to body.standalone. The plugin adds no markup or styling. It uses the Navigation API, available in Chrome, Edge, Firefox 147 and Safari 26.2 (including iOS); in older browsers nothing happens. This replaces LH Navigation Listener, which is to be retired; its progress bar is not carried over.
- 1.34.0: Three misspelt method names are corrected. get_webb_app_background_colour() is now return_background_colour(), return_shorcut_menu_array() (on the manifest class) is now return_shortcut_menu_array(), and has_elegible_image_extension() is now has_eligible_image_extension(). The plugin itself now calls only the new names. The old names still work as deprecated wrappers that call the new ones and raise a deprecation notice when debugging is on, so any code still using them shows up in the debug log; nothing on this network calls them. No change to what the plugin outputs.
- 1.33.0: Internal restructuring, with no change to what the plugin does. The 1,800-line main class is split into one class per job in includes/: the manifest, the service worker endpoint and page script, the head tags (icons, startup images, Apple tags), the shortcut menu icon field, the offline page, and the scheduled rewarm. The option, image and script helpers they share move into three traits used by the main class, so every existing static method stays callable on LH_web_application_plugin (including create_apple_startup_sizes()) and in its child classes, with no wrappers needed. The main file keeps the singleton, identity, plugin_init() with the lh_web_application_init_plugin gate, write_log() and the activation, deactivation and uninstall hooks. The shortcode moves to the app install class. Unchanged: every hook and filter, the script handles, the return_wp_nonce admin-ajax action, option names, the rewrite endpoints and the /offline/ slug. Verified against a baseline taken before the change: the manifest, service worker and head tags are byte-identical on lhero.org and Princes Park Touch.
- 1.32.0: Faster pages in the installed app, a bounded offline page cache and a more complete manifest. – Navigation preload: the service worker now enables navigation preload, so on browsers that support it the page request starts while the worker is still waking up. Most noticeable on slower phones. Navigations the worker does not handle (wp-admin, login, REST API, URLs carrying a nonce) are answered from the same preload, so they are never requested twice. – The saved-pages cache is now capped, like the images cache: by default it keeps the 300 most recently used pages and drops the least recently used first. The start page and the offline page are never dropped. Developers can change the cap with the new lh_web_application_pages_max_entries filter. – The web app manifest now includes lang and dir, decodes HTML entities in the site name and tagline, and leaves out description when the site has no tagline. Unchanged on purpose: the Apple Web App Capable setting stays (iOS still needs it for startup images), and the daily periodic sync registration stays because the BuddyPress Live Content and Web Push Notifications service worker includes both handle it.
- 1.31.2: iOS startup images now appear. With Apple Web App Capable set to Yes, the plugin printed only the unprefixed mobile-web-app-capable tag, and iOS does not show startup images without apple-mobile-web-app-capable. Both tags are now printed.
- 1.31.1: iOS startup images are now cropped from the original upload. WordPress keeps a scaled-down copy (at most 2560 pixels) of large uploads, and the image CDN never enlarges an image, so cropping the scaled copy returned the whole image unchanged for most iPhone screens, which iOS ignores. The startup image is now sent to the CDN from the original upload, also when the scaled copy has lost its record of the original, and only screens no bigger than that original get a startup image link.
- 1.31.0: iOS startup images now work with LH Image CDN, and cover current iPhones and iPads. The startup image was never shown: each size had to come back at exactly the device’s pixel size, but the image CDN fits an image inside the requested box rather than cropping it, so any image without the device’s exact proportions (such as a landscape photo) produced no startup image links at all. Startup images served through an image CDN built on Jetpack’s (such as LH Image CDN) are now requested as exact-size crops; served locally, a size is still only used when a crop of exactly that size exists. The device list is replaced with every current screen size, from the iPhone SE to the iPhone 17 Pro Max and iPhone Air, and from the iPad mini to the 13-inch iPad Pro, with landscape images for iPads. The old list stopped at the iPhone X and had wrong heights for the iPhone 8 and 8 Plus. The list can be changed with the new lh_web_application_apple_startup_sizes filter.
- 1.30.1: Plugin Check fixes and readme housekeeping. The settings save now reads its text fields in the same function that checks the nonce and capability, instead of in a separate helper; saving behaves exactly as before. The settings page passes the plugin’s text domain directly to every translation call. Removed the load_plugin_textdomain() call: the plugin ships no translation files, and WordPress loads translations automatically. The on-disk readme.txt is removed after the reconstruction check passed: compile-readme returns no errors in md or txt, its FAQ is now the FAQ page of the portfolio entry (the dead manifest validator link and the outdated iOS answer were deliberately rewritten), and versions 1.00 to 1.28 were backfilled as changelog entries. Readmes are now compiled from the portfolio entry at push and into zips. Also removes libraries/lh-register-file-class.php, which nothing has loaded since 1.30.0.
- 1.30.0: Standards release, with smarter offline cache housekeeping. The service worker now remembers when each cached file was last used: static files (CSS, JavaScript, fonts) unused for 180 days are removed, so replaced ?ver= copies no longer pile up, and cached images are kept however old but capped at 150, dropping the least recently used first. Both limits can be changed with the lh_web_application_static_max_age_days and lh_web_application_images_max_entries filters. The plugin now loads on plugins_loaded behind the lh_web_application_init_plugin filter, reads its version from the plugin header, registers its scripts with core WordPress APIs instead of the bundled LH_Register_file_class, and no longer wraps its own classes in class_exists guards. All output is escaped and all settings input sanitised; settings remain per site (manage_options). Colour fields now accept hex colours only, and emptying a colour or the offline message now clears it. The settings page markup is fixed and fully translatable, and the shortcut menu icon field is now labelled Shortcut icon. Removed dead code: the unused Service worker cache list menu location, the unread is_pwa cookie, localforage visit and install counters (which threw errors on sites without LH Localforage), console logging, an unused direct database query, handle-offline.js, and the unused add-to-homescreen stylesheets and images. The Install App script now finds its own tag reliably. Adds index.php guards to every folder, and the Requires at least (6.3), Requires PHP (7.4) and License URI header fields. Requires WordPress 6.3 or later.
- 1.29.5: Install prompt for everyone, plus fixes. The Install App prompt (the install menu item and its dialog) now works for every visitor, not only the first user account; its 100 KB install bundle still loads only when the prompt is used and is kept out of the service worker precache. The image pickers on the settings screen (manifest icon, iOS icon, iOS startup image) now work, as do the icon pickers for shortcut menu items on Appearance > Menus; their script was an empty file. Deleting the plugin no longer causes a fatal error: it now removes the plugin’s settings, scheduled events and menu icon data from every site, leaving uploaded images alone. The manifest no longer breaks when the chosen shortcut menu has been deleted or is empty, and a background colour taken from the theme now has its leading # so it is valid in the manifest. Removed a hook that tried to load a script that does not exist.
- 1.29.4: Pages shown from the offline cache now say so. When the service worker serves a saved copy of a page because the visitor is offline, it marks the page with the time the copy was saved, and a bar at the top of the page reads “You are offline. This is a saved copy from (day, date and time)”. When the connection returns the bar changes to “You are back online” with a Reload button. The body gets the class lh_web_application-served-from-cache, the bar’s minimal inline styling has zero specificity so themes can restyle it, and the wording is translatable.
- 1.29.3: Images are now cached for offline use. Cross-origin images, such as those served through Photon (i0.wp.com) or Gravatar, were never cached because the browser’s own image request returns an opaque response; the service worker now fetches them itself in CORS mode, falling back to the normal request (uncached) for hosts that do not allow CORS. Images have their own cache, capped at the 150 most recent, which skips responses marked no-store or private and is cleared along with the pages cache whenever the signed-in user changes. An image that is not cached while offline still shows the grey offline placeholder.
- 1.29.2: Service worker caching fixes. Offline pages now work: a page that cannot be fetched is served from the cache, and only falls back to the offline page when it has never been viewed (previously the fallback code referenced an undefined variable and never served the cached copy). Only successful same-origin HTML pages are kept for offline use; wp-admin, admin-ajax, the REST API, wp-login.php, URLs carrying a nonce, and responses marked no-store are never cached. The pages cache is cleared whenever the signed-in user changes (logout, session expiry or a different account), using a hashed user marker the plugin now prints in the head. The offline page has its own cache, renamed when the offline message changes. Installing a new worker now waits for its precache, and one failed URL no longer blocks it. Activating a worker no longer deletes the static cache or caches belonging to other plugins.
- 1.29.1: Security and cleanup release. Removed leftover debug output from the Settings → Web Application screen: a shell_exec(‘which git’) call and shell_exec/mod_xsendfile status text that also broke the form table markup. Hardened the return_wp_nonce admin-ajax endpoint used by service worker includes (LH Web Push Notifications, LH BuddyPress Live Content) to fetch a REST nonce: it now only ever issues the wp_rest nonce, ignoring any requested nonce name, and responds via wp_send_json(). The response format (a JSON-encoded nonce string) is unchanged. Moves the version to three-segment SemVer.
- 1.28: Multiple changes.
- 1.27: Multiple changes.
- 1.26: Bundled assets.
- 1.25: Additional functionality.
- 1.24: Old caches are cleared.
- 1.23: The service worker checks the response status.
- 1.22: Better Apple support.
- 1.21: Removed version strings; file modification times are used everywhere instead.
- 1.18: The manifest is written on activation.
- 1.16: Static manifest.
- 1.15: Extra error checking.
- 1.14: Bug fix.
- 1.13: Major improvements.
- 1.10: Various improvements.
- 1.08: Multiple fixes.
- 1.07: Better service worker and file loading.
- 1.06: Moved inline code into its own file.
- 1.05: Add to home screen support.
- 1.04: ETag support.
- 1.03: Uses isset checks.
- 1.02: Rewrite rules are flushed by cron, and other fixes.
- 1.01: Added options and documentation.
- 1.00: Initial release.
Changelog
If you like this plugin please consider making a donation to further development