v1.1.0
Added
-
French (fr_FR), German (de_DE), Italian (it_IT), Spanish (es_ES), Brazilian
Portuguese (pt_BR) and Dutch (nl_NL) translations of every user-facing
string, including the redesigned settings screen and the plural form used
for post counts. WordPress selects the right one from the site's own
language setting. -
Rebuilt settings screen. The four stacked
form-tablesections are now
five panels behind a sidebar: Dashboard, Discovery, Content, AI crawlers,
Identity. Each nav entry carries an icon, a label and a one-line description,
and the whole screen sits in a single surface rather than floating on the
admin's grey.- The panel is in the URL (
?tab=…), so reloading, bookmarking, and the
redirect WordPress performs after saving all come back to the panel you
were on. - The switcher is a list of real links and every panel is in the DOM, so the
screen still works with JavaScript disabled — and, more importantly, saving
from one panel can never wipe the settings on the other four. - Switches are styled checkboxes, not scripted divs: they submit, they reach
the tab order, and a screen reader announces them correctly. - Arrow keys, Home and End move between panels, per the ARIA tabs pattern.
- The panel is in the URL (
-
Health checks on the dashboard. Five conditions that silently break the
plugin are now reported: plain permalinks (which make every endpoint 404), an
unwritable uploads directory, "discourage search engines" (which withholds the
robots.txt directives), missing generated files, and the availability of the
Abilities API. None of them were visible from the settings form before. -
Each generated file is shown with its real size, its modification time in the
site's own date format, a link, and a copy-URL button. -
No web font, no icon font, no sprite: the icons are inline SVG. An outbound
request from wp-admin would contradict the plugin's own no-network rule. -
Quality pipeline: Pest test suite, PHPStan at level
max, PHPCS (WordPress
security, escaping, i18n and PHP compatibility rulesets), Laravel Pint, and a
parallel syntax lint — all run on every push by GitHub Actions. -
End-to-end workflow that installs a real WordPress, activates the plugin and
asserts each endpoint answers with the right status and content type, across
the supported PHP and WordPress matrix. -
Release workflow producing an installable zip built from
git archive, so no
development file can reach a distributed build. -
uninstall.php: deleting the plugin now removes its options, its scheduled
event, its per-post transients and its generated files — on every site of a
multisite network. -
Artifactenum as the single description of the four generated files, and a
FileStorethat writes them atomically via a temporary file plusrename().
Fixed
- The site-description placeholder example read "A municipal opposition site
covering local council decisions, budgets and public consultations." — a
leftover from the plugin's own origin, not a generic example for a
distributable plugin. Replaced with a neutral one, in every shipped language. - Password-protected posts were exposed through the Markdown endpoint. The
converter calledthe_contentdirectly, bypassing the password check, and the
result was cached in a transient shared by every visitor — so one reader
holding the password published the content to everyone. Protected posts now
answer 404 and are excluded fromllms.txtandllms-full.txt. - Fenced code blocks were never produced. The inline
<code>rule ran before
the<pre><code>rule and consumed its inner element, so every code block came
out as inline backticks.<pre>without a<code>child is now handled too. - Hard line breaks were silently dropped.
<br>was converted to the two
trailing spaces Markdown requires, and the cleanup pass then trimmed them off. - The
enable_llms_txtsetting did nothing. The endpoint was registered
unconditionally; unchecking the box had no effect. - Deactivating left the endpoints answering 301. The deactivation hook called
flush_rewrite_rules()while the plugin was still loaded for that request, so
the rules it meant to remove were immediately re-registered./llms.txtkept
matching a rule whose query var no longer existed, and WordPress answered with
a canonical redirect instead of a 404. /?format=mdreturned 404 on a static front page.WP_Queryonly
substitutespage_on_frontwhen the query carries nothing beyondpreview,
page,pagedandcpage;formatis one more, so the home request fell
through to the blog index and never resolved to the page.AiDirectives::addDirectives()declared its second argument asbool, but
do_robots()passesget_option('blog_public')— the string"1"or"0".
The call only worked because WordPress core does not declarestrict_types.- Failed writes are reported instead of ignored.
wp_upload_dir()errors, a
read-only uploads mount or a full disk now surface in WP-CLI, in the admin
regeneration button and in the return value ofregenerateAll(). - Settings read from the database are normalised into a known shape, so a
corrupt or partially written option row degrades to defaults rather than
throwing aTypeErrordeep inside a generator. - A filter callback returning something other than a string no longer breaks
generation; the unfiltered value is used instead. - Every admin field is escaped on output, and the regeneration script moved from
an inline<script>block to a properly enqueued asset. handleRegenerate()checks the capability before the nonce, so an
unprivileged user is told they may not do this rather than that their nonce
expired.- The Markdown
<link rel="alternate">is no longer advertised for
password-protected or noindex posts. - Regex conversion steps degrade gracefully when PCRE hits its backtrack limit
on pathological markup, instead of blanking the whole document.