Skip to main content
Changelog

What's new

A full history of every feature, fix, and improvement. No "minor tweaks" that secretly rewrote everything.

0.9.10 — 30 March 2026

Features

  • Standalone updater — a self-contained update.php wizard that walks you through upgrading between versions. Upload two files, visit the URL, log in with your admin credentials, and follow the steps: backup confirmation, dry-run pre-flight checks (PHP version, ZipArchive, DB privileges, disk space, writable directories, ZIP hash validation), automatic database backup, migration runner, file extraction with rollback capability. If something goes wrong, click one button and everything goes back the way it was. If everything's fine, click the other button and the old files disappear. Works on shared hosting with no shell access — because that's the whole point.
  • Activity log and security log pagination — both tabs now paginate at 50 rows per page with Newer/Older navigation. Subtitles show the total count. Previously it loaded everything at once, which was fine when "everything" was 30 entries and less fine when it was 3,000.
  • Save & Exit — new option in the split save dropdown on page, asset, and partial editors. Saves and returns you to the Pages list. For when you've made your change and don't need to stare at the editor anymore.
  • Sitemap editor save button — moved to the navbar as a split button with Save & Exit. The old inline buttons at the bottom of the page are gone. Consistency is a feature.
  • public/images/ directory — a new home for developer template images (icons, backgrounds, design elements) that aren't meant to be CMS-managed. Created by the installer. Documented in Directory Structure and Image Attributes. Your hero background goes in uploads/; your decorative SVG arrows go in images/.
  • update.php in security checklist — the dashboard now auto-detects update.php on disk, rated High risk, same as the install.php check. The environment report includes it too. Because the updater can overwrite your entire CMS, and leaving it on the server after you're done is the kind of thing that keeps security auditors employed.

Bug Fixes

  • Activity page returned a 500 error. The count queries called Database::fetch(), which doesn't exist. The method is called fetchOne(). It has always been called fetchOne(). In our defence, fetch() is what every other database library calls it.

Legal & Compliance

  • Privacy policy updated for GDPR compliance — legal basis for processing, international transfer disclosure, data portability rights, right to withdraw consent, right to complain to a supervisory authority, and a proper data controller section. Umami (analytics) and Deftform (contact forms) disclosed as third-party processors.

0.9.9 — 29 March 2026

Features

  • Nav links in the page tree — add external URLs to your site navigation without creating a CMS page for them. Drag them into position in Admin → Pages. New split "Add Page / Nav Link" button so the workflow fits naturally alongside page management. Nav links stay top-level — they can't be nested, because dropdown menus inside dropdown menus is how you end up on a UX hall-of-shame thread.
  • {cms:year} template tag — outputs the current four-digit year at render time. Drop it into your footer copyright line and never manually update the year again: © {cms:year} Acme Co. One less thing to forget every January. You're welcome.
  • {cms:nav:pages:bootstrap} preset — using Bootstrap 5? This variant outputs navbar-nav, nav-item, nav-link, and dropdown markup so you don't have to manually wrangle the default nav output into Bootstrap's preferred shape. Pages with child pages become dropdowns automatically. No JavaScript configuration required — just the tag.
  • New default home template — clean hero section with a real background image (data-cms-bg-image) pre-loaded with a bundled 1920×800 photo. Editable heading and subheading regions. Enough structure to demonstrate the CMS without overwhelming a new user. Enough design that the fresh install doesn't look like a cry for help.
  • Quick-start guide page (/quick-start, unlisted) — a developer reference page seeded on every fresh install covering editable regions, image replacement, template tags, and assets. Links to the documentation and video tutorials. Unlisted so it stays out of the nav and sitemap. Read it, learn from it, delete it. It won't be offended.
  • Snapshot export/import preserves image slots — previously, taking a snapshot and importing it on a clean install brought your content but lost the image slot database records. The first image replacement would cheerfully create a duplicate slot and pretend everything was fine. Now the manifest includes image slot records and they're fully restored on import.
  • Snapshot export/import preserves nav links — nav links are now included in the manifest and restored on import alongside pages and regions. A snapshot that drops half your navigation is not a snapshot — it's a surprise.
  • Upgrade docs rewritten — clearer step-by-step instructions, a table of which files are safe to overwrite vs. which ones you must never touch, manual migration instructions for cPanel/phpMyAdmin and SSH, and a troubleshooting section for the three errors people actually hit. The old docs said "run the migrations." Thanks. Very helpful.

Security

  • A crafted snapshot ZIP could contain nav links with javascript: URLs, which would be inserted into the database and rendered as live href values in the front-end navigation. URLs not starting with /, http://, or https:// are now rejected at restore time — and also at render time in the template parser, because trust but verify is for diplomats and we are not diplomats.
  • Snapshot settings restore converted from a three-key denylist to an explicit twelve-key allowlist. A crafted snapshot could previously overwrite site_url, dev_mode, SMTP credentials, or any other setting. "Block three bad things" is not a security strategy. "Allow twelve good things" is.
  • Nav link target value validated on snapshot restore — only _self and _blank are accepted. Anything creative defaults to _self.
  • File revision records (runt_file_revisions) are now cleared before importing a snapshot or template, preventing stale records from surfacing incorrect content in the file history panel after import. Ghost revisions from a previous life are not a feature.

Bug Fixes

  • All five auth screens (2FA challenge, email OTP, recovery codes, forgot password, reset password) now respect your dark/light mode preference. Previously only the login page got the memo. The other five showed up in blinding white regardless of your theme — like a flashbang with a password field.
  • Content Security Policy now scoped to admin routes only. Previously it applied to every page you visited while logged in, which meant your client's Google Map, contact form, YouTube embed, and weather widget all stopped working the moment you logged in to check something. The CSP now stays in the admin panel where it belongs and leaves front-end pages alone.
  • "Send test email" button on Settings actually sends a test email now. It was inside a nested <form> — which is invalid HTML — so the browser helpfully promoted it to submit the outer SMTP settings form instead. You'd click "Send Test", see "Settings saved", and wonder why no email arrived. Fixed with the form attribute. The email arrives now.
  • Environment report "Copy to Clipboard" button was silently blocked by the Content Security Policy. The inline script was missing a CSP nonce. It had one job.
  • Hero text colour toggle now works. The heading and subheading had hardcoded color: #fff, which meant clicking "Toggle to dark text" did absolutely nothing. Changed to color: inherit so the variant class can actually do its thing.
  • Help link in the admin sidebar now hidden for editors. It pointed to the RuntCMS-branded documentation — which is fine for the developer, but not for the client who thinks the CMS is called "Your Company Portal" because you white-labelled it. Editors already have a help button in the runtbar.
  • Snapshot import now restores your JS files. The js/ directory was missing from the snapshot ZIP entirely — only css/, pages/, uploads/, and templates/ were included. If you imported a snapshot on a fresh install and your JavaScript wasn't working, this is why.
  • CSS and JS assets now reappear in Admin → Pages after importing a snapshot. The files were being restored correctly, but the database records weren't — so the files existed but the admin had no idea about them.
  • Hero background image now included in the package and copied to uploads during install — the default home page and quick-start page showed a black hero section on a fresh install because the image was never placed in cms/uploads/.
  • Footer copyright tag simplified — the installer was seeding footers with {cms:year}, which never got processed because template tags in user-editable regions are intentionally blocked as an SSTI safeguard. The footer now shows the actual year from install time.
  • Saving a nav link now returns you to Admin → Pages instead of leaving you on the edit form with no obvious next step.
  • Nav link position field removed — position is set by drag order in the page tree, not a form field.

0.9.8 — 27 March 2026

Features

  • Coming Soon mode — put visitors in the waiting room while you actually finish the site. Toggle it in Settings, add a heading and message, and unauthenticated visitors see a branded holding page with your logo and a subtle login link so you can still get in. Returns HTTP 503 with Retry-After and noindex so search engines know to come back later.
  • Environment report — one-click plain-text diagnostics file covering PHP version, MySQL version, loaded extensions, key INI values, HTTPS status, pending migrations, and directory writability. No passwords. No excuses for "it works on my machine." Download it and attach it to a support request.

Security

  • Full security audit against PHP and JavaScript best-practice checklists. 16 things found. 16 things fixed. Highlights:
    • declare(strict_types=1) added to all 59 PHP source files — because silent type coercion is how you get bugs that only show up on Fridays.
    • Logout changed from a GET link to a POST form with a CSRF token — the old version could be triggered with an <img src> tag on any page the admin visited. That's a bit forward.
    • object-src 'none'; base-uri 'none' added to the admin Content Security Policy — closes two obscure but real injection vectors that the original CSP politely left open.
    • Password reset and welcome emails now use the configured site_url setting instead of the raw Host header — prevents an attacker forging the header to hijack reset tokens via a link in your inbox.
    • DOMPurify bundled into runtbar.js — sanitises database content before every innerHTML write in the toolbar. Server-side sanitisation already handles the heavy lifting; this is the seatbelt for the seatbelt.
    • SRI integrity hashes added to all 9 CodeMirror CDN tags — if the CDN is compromised, the browser won't execute it.
    • esbuild updated to fix a moderate CORS CVE in the dev server. Low real-world risk, but the number in the audit report was annoying.
  • CSRF protection added to the security checklist endpoint — JSON Content-Type provides some CSRF resistance, but "some" wasn't good enough. Proper X-CSRF-Token header validation now required.
  • Settings cards now properly isolated — saving your SMTP config no longer silently disables Coming Soon mode. Checkbox values should only disappear when you uncheck them yourself.

Bug Fixes

  • Snapshot import overwrote admin.css, runtbar.js, and runtbar.css — the compiled admin panel assets were bundled into the snapshot ZIP and extracted on import, replacing the installed version's files with whatever version you happened to export from. The admin panel then looked like nothing, because it was. These files are now excluded from both export and import. Old snapshots that already contain them are also safe — the files are silently skipped on extract.
  • Snapshot import crashed with "Unknown column 'content'" on a fresh install — region and global region content was being written directly to runt_regions, which hasn't had a content column since revisions were introduced. Content is now correctly inserted into runt_revisions and the region is pointed at it.
  • Admin panel broke on back/forward navigation — each page load generates a fresh CSP nonce, but the browser's back/forward cache restored the old HTML with the old nonce, making the nonce no longer match. The browser then silently blocked Alpine.js. The dashboard looked fine until you pressed Back, then it looked like a checklist with no CSS had been thrown at a plain white page. Cache-Control: no-store now prevents admin pages from entering bfcache.
  • Upgrade page gave an unhelpful "check database permissions" error when a migration needed ALTER privileges the CMS user doesn't have. It now tells you exactly what privilege is missing and shows the SQL to paste into phpMyAdmin or run via SSH — because knowing there's a problem and knowing how to fix it are two different things.
  • Installer done page checklist: text and <code> blocks were overflowing the card on some items. The disable_functions line, in particular, was escaping the layout entirely and going off to do its own thing.
  • SVG files can now be deleted from the Image Library. The delete button worked fine — the filename validator just refused to acknowledge that SVGs existed.
  • Environment report now correctly detects install.php on both dev layout and flat shared-hosting installs. Previously reported "not found" on production even when it was sitting right there.
  • runt_image_files.location column added to installer schema — was missing, causing a fatal error on the very first image upload on a fresh install. A warm welcome.
  • cms/templates/ directory with .htaccess now created by the installer and included in the release package. Referenced in the docs, the backup system, and the environment report. Just not, until now, on disk.
  • .htaccess files for cms/pages/ and cms/uploads/ bundled in the release package as a safety net alongside the installer.

Accessibility (WCAG 2.2 Level AA)

  • Coming Soon page: login link contrast fixed in both light and dark mode; <main> landmark added; dark logo marked aria-hidden so screen readers don't announce the site name twice.
  • Settings page: aria-live region announces "Copied!" when the clipboard button is used; external links get a screen-reader-only "(opens in new tab)" label.
  • Security checklist: expandable detail text now uses native <details>/<summary>; auto-verified checkboxes get aria-description="Auto-verified".
  • Template stats bar: toggle buttons now carry aria-pressed state so screen readers know when highlighting is active.
  • Save dropdown: arrow key navigation (Up/Down/Home/End) and Escape handling added — role="menu" implies keyboard nav, so now it has it.
  • Sitemap editor: textarea gets a visually-hidden <label> and aria-describedby pointing to the hint text.
  • Image Library: role="alert" added to error messages so they're announced without the user having to go looking.

0.9.7 — 25 March 2026

Features

  • SVG uploads — admins can now upload SVGs. Each one passes through a DOM-based sanitizer that strips <script>, <foreignObject>, event handler attributes, external references, data: URIs, and anything else that looked at it funny — before it touches disk. Bitmap images still go through GD for EXIF stripping. SVGs bypass GD and go through the sanitizer instead.
  • Post-install security checklist — a "Finish Setup" card on the dashboard with 7 items rated High, Medium, or Low risk. Three are auto-verified by the CMS (HTTPS active, install.php deleted, display_errors off). The other four are on you. Card hides permanently once everything is ticked.
  • Template stats bar — live count of regions, globals, images, and partials below the page code editor after every save. Click any stat to highlight all matching attributes in the code. Duplicate region names flagged in amber, because you probably didn't mean to do that.
  • Save & View Page — new option in the Save Page dropdown. Saves the page, then takes you straight to the live front-end URL in the same tab. The Edit button on the runtbar brings you right back.
  • Sitemap extra URLs — add URL paths for docs pages, marketing pages, or anything the CMS doesn't manage directly. One per line. Must start with /. This is not negotiable.

Bug Fixes

  • Apostrophes in SEO fields no longer kill the entire runtbar. A single quote in "What's new" was breaking out of the JavaScript string literal in the Alpine x-data attribute, taking every toolbar button with it. Properly escaped for JS context before HTML-encoding now.
  • Drag-and-drop page sort order no longer overwritten when editing a page. Saving a page form was helpfully resetting the position you just spent time arranging.

0.9.6 — 23 March 2026

Security

  • Content Security Policy removed from public-facing pages. Clients embed Google Maps, YouTube, Vimeo, Elfsight widgets, payment forms, and approximately 47 other third-party tools. The CSP disagreed with all of them. It has been reassigned to the admin panel where it belongs and actually makes sense.

Bug Fixes

  • Fresh install: missing database table caused a fatal error on any page save. Welcome to RuntCMS, please enjoy your 500. Table now created by the installer.
  • Fresh install: installer done page never rendered — it was deleting itself before finishing the redirect. It now waits until you've actually seen the page.
  • Settings save silently enabled Development Mode on fresh installs — a ?? 'server' default that should have been ?? 'local'. It shipped. We noticed. Fixed.
  • Installer redirected to a 404 after completion. Done page (security checklist + DB hardening instructions) now renders correctly before the file removes itself.
  • 404 page showed raw {cms:site:favicon} and {cms:system:runtbar} template tags instead of processed content. The template parser was not invited. It is now.
  • Fresh install showed 6 pending migrations — migration seeding was silently skipped due to an out-of-scope variable. Fresh installs now correctly show "up to date".
  • Download Full Backup returned a 500 error. A column that didn't exist was being selected. It was not selected for long.
  • SQL backup exported 8 of 17 tables. The other 9 were simply not included. All 17 now make it into the ZIP.
  • Template export re-import wiped all page HTML. Pages are now included in the export. Restoring a template no longer also restores the regret of losing your content.
  • Applied migrations list was shown on fresh installs with nothing pending. Now only shown when there's something to look at.
  • Nav view ran a live database query on every admin page load. Refactored to a per-request cached static method. The database appreciates the break.
  • User name in delete confirmation dialog was double-escaped — showing &amp; instead of &. HTML encoding is good. Doing it twice is not.

Security Hardening

  • Theme cookie output escaped in all admin view files — defense-in-depth for a value that should never contain anything interesting anyway.
  • Login redirect now rejects protocol-relative URLs like //evil.com — open redirect prevention.
  • Password reset endpoint rate-limited to prevent email flooding.
  • Migration failure messages no longer expose raw PDO error details to the browser. Logged server-side instead.

Improvements

  • Pending migration count cached per-request — views no longer query the database directly on every page load.
  • Silent failures hardened throughout — suppressed errors and empty catch blocks replaced with proper error_log() calls.
  • Download button renamed to "Download Full Backup" to distinguish it from the partial export options.

0.9.5 — 22 March 2026

Features

  • Export Template — generates a ZIP of the site structure with uploaded images replaced by neutral placeholders. Hand it to a client, use it as a starting point for the next project, sell it in the template store.
  • Export Snapshot — full-content ZIP for restoring a site to this exact moment after a clean install. The "oh no" button.
  • Import Template / Import Snapshot — moved to the Backups page with admin password confirmation, because irreversible actions deserve a speed bump.
  • Page hierarchy correctly restored on template import.
  • Migration runner — shows pending and applied database migrations with one-click apply. For when you upgrade between versions and need the schema to catch up.
  • Upgrade nav item with pending count badge — so you can't miss it.
  • File revision history — CSS assets, JS assets, HTML page files, and Global Partials record up to 10 revisions per file when edited through the admin. Line-level diff view and "Load into Editor" restore. Gated by Development Mode setting.
  • Development Mode setting — Local (default, no file revisions, use Git) or Server (records revisions for direct on-server editing). Defaults to Local so a fresh install doesn't surprise anyone.
  • Save-in-place with toast notification — editor pages stay open after save instead of redirecting. A green toast slides in, auto-dismisses after 5 seconds, has a close button.
  • History button in the topbar on all file, partial, and page editors.
  • Cmd+S / Ctrl+S keyboard shortcut wired to save on all editors.

Improvements

  • Upgrade page — explanatory "About This Page" card with collapsible manual migration instructions for cPanel and SSH environments.
  • Page editor — Organisation section moved below SEO. More logical order, debatable if anyone noticed.

Security

  • Installer self-deletes after setup with confirmation — it creates an admin account and resets the database, so it really should not linger.
  • Admin nag banner displayed until the installer file is confirmed deleted.
  • Post-install DB hardening advice with specific REVOKE SQL commands on the done page.
  • .htaccess now blocks direct web access to .md files and the migrations/ directory.
  • CHANGELOG.md and SECURITY.md excluded from the release package — not needed at runtime and would be web-accessible on a flat shared-hosting install.

Bug Fixes

  • JS asset form auto-generated filenames with the wrong extension. A CSS file named something.js is a bad time for everyone.
  • Pages admin "Add New" dropdown replaced with contextual buttons. The old one just got in the way.
  • Upgrade page returned a 500 on restricted database users — runt_user doesn't have CREATE TABLE privilege and shouldn't need it. Handled gracefully now.

0.9.4 — 21 March 2026

Features

  • Security event log — logins, logouts, password changes, 2FA enrollments, and failures tracked with IP addresses and timestamps. Downloadable as CSV. For when you need to know who did what and when.
  • Activity page Security tab (admin-only) with CSV export.
  • Version number displayed in the admin sidebar — so you always know which one you're actually running.
  • Split Save button on Edit Page — dropdown with "Save as Draft", "Save as Unlisted", and "Publish". No more publishing something before it's ready because the only button said Save.
  • Image upload path noted on the Images page — for the inevitable "where did my file go" question.

Improvements

  • Admin CSS refactored — all inline styles extracted into a single stylesheet. The kind of cleanup that saves future-you from hunting through views for that one rogue style="margin-top:6px".
  • Font split — Montserrat for headings, Open Sans for body text.
  • White-label auth titles — all login, reset, and 2FA views use the site name setting instead of "RuntCMS".
  • Tests made fully self-contained — no shared state between test runs.

Accessibility

  • Admin panel audited and hardened to WCAG 2.2 Level AA.
  • Accessible confirm modal replaces native confirm() dialogs — because browser dialogs are not keyboard-navigable and look like they're from 2003.
  • Full ARIA support on dropdowns, menus, tables, breadcrumbs, and forms.
  • Dynamic state announcements via aria-live regions.
  • Keyboard navigation and focus management throughout — Escape closes modals, Tab moves logically, focus returns where it came from.
  • Visible :focus-visible rings and prefers-reduced-motion support.

Security

  • Backup SQL dumps redact sensitive fields — passwords stay hashed, tokens stay out.
  • Template injection prevention — user content neutralised before being processed by the template parser.
  • SVG sanitisation with DOM-based allowlist — predates the full SVG upload feature but the foundation was here.
  • Remember-me token invalidated on password change — logging in somewhere else shouldn't keep a compromised session alive.
  • Unified password policy enforced across all flows — registration, reset, and profile change all use the same rules.
  • CSV export formula injection prevention — cells starting with =, +, -, or @ are prefixed. Old Excel trick, still relevant.
  • Installer hardened — atomic lock file, rate limiting, POST-only reset.

0.9.3 — 17 March 2026

Features

  • Archived images moved to non-public storage — replaced images are no longer web-accessible. Old versions sit in a private directory and are served via authenticated PHP proxy only. Your client's old headshot is not Google-indexable.

Security

  • HSTS header enabled — forces HTTPS for the duration of the max-age. Not optional once you're live.
  • SQL parameterisation extended to timezone queries — no raw values in SQL, ever, anywhere.
  • CSRF token regenerated on remember-me session restore — long-lived cookies shouldn't carry stale tokens.
  • Absolute session timeout — 8-hour default. Being logged in for three days straight on a shared computer is not a feature.
  • Draft page protection — editors can no longer reach draft page content via the API, even if they know the slug.
  • Production error display suppressed — display_errors Off enforced, errors logged server-side.

Bug Fixes

  • SortableJS page nesting — drag gestures for nest/un-nest were broken by native HTML5 drag suppressing mousemove. forceFallback: true fixed it.
  • Sticky formatting toolbar and Tiptap image embed edge cases resolved.

0.9.2 — 15 March 2026

Features

  • SEO section added to runtbar help modal — so editors know the meta title and description fields exist before launch day.
  • Default favicon seeded on fresh install — the browser tab no longer shows a blank page icon out of the box.

Bug Fixes

  • Installer now aborts if the database already has user accounts — prevents accidentally re-running setup on a live site.
  • Broadened dirty-database detection in installer — checks more tables, not just runt_users.
  • Correct home page regions seeded during install — the demo content now actually uses the regions the home template expects.

0.9.1 — 14 March 2026

Features

  • Text colour variants for background images — toggle between light and dark text on a section when you replace the background photo and suddenly can't read anything.
  • Runtbar help modal for editors — a quick-reference overlay so they don't have to ask "how do I save" more than once.
  • Documentation site — the thing you're reading right now had a sibling. It's here.
  • Marketing homepage — runtcms.com went from "nothing" to "something".
  • Demo site template — a restaurant theme covering the three core editing scenarios: headshot, menu prices, contact info.

Improvements

  • Flat package layout — everything extracts into one folder, uploads to public_html/, done. No moving files around.
  • Tiptap class preservation on edits — custom CSS classes on elements no longer vanish when the content is saved.
  • Lazy region provisioning — editable regions are created in the database on first page load, not at page creation time.
  • CSP fixes for broader template compatibility.

0.9.0 — 8 March 2026

Initial Pre-release

  • Auth, sessions, CSRF, brute-force protection — the stuff that has to work before anything else matters.
  • Template parser, page rendering, routing — a PHP CMS that parses {cms:tags} and turns HTML files into editable pages.
  • Inline editing via Tiptap, AJAX save, HTML sanitisation — click on text, edit it, save it. That's the whole pitch.
  • Revision history with diff view and one-click restore — for when "save" and "undo" mean different things to different people.
  • Image upload, inline image replacement, image library — hover over a photo, click Replace, upload something better.
  • Page hierarchy with drag-and-drop reorder — nested pages, sortable tree, the nav tag handles the rest.
  • Dashboard, sitemap.xml, robots.txt, unlisted pages — the table stakes of any CMS worth the name.
  • User management, editor/admin roles — admins see everything, editors see the live site with a toolbar at the bottom.
  • Mobile runtbar, page cloning, global partials — the bits that make it practical rather than just possible.
  • Forgot password, TOTP 2FA, email OTP 2FA — security features that are actually set up, not just planned.