Added
-
Core updates take a single click (2026-09-23). Starting with this version, Administration → Monitoring reports new versions of NeoFrag Reborn and installs them: site backup, package checksum verification, applying the package, database update, and a return to the backup if anything fails. This is the first version released through this path; it was tested end to end on a fresh site before release. A site on 1.1.0 does not have this button yet: it moves to 1.2.0 by hand, by replacing its files. For maintainers:
tools/build-release.phpproduces the three files to publish together (neofrag-reborn-update-<v>.zip,version.json,checksum.json); the procedure is indocs/guide/marketplace.md. -
Announcements on Discord (2026-09-23). A webhook pointing to a Discord channel now receives a real message, with title, link and color: new news item, new article, new member, new comment, new forum topic. Until now Discord rejected what the module sent, even though its description promised Discord.
-
"So-and-so is live" (2026-09-23). When a Twitch or YouTube channel followed by the "Live status" widget goes live, the webhooks subscribed to the "Channel goes live" event announce it, only once per stream, with the stream's thumbnail.
-
The demo also speaks English in its content (2026-09-23): its news items, articles, pages, categories and galleries have an English version, which shows off the multilingual feature.
-
The whole product speaks all six languages (2026-09-23). The administration, the modules, the widgets, the themes, the settings, the buttons, the messages and the scripts: nearly 800 strings were written directly in French and stayed that way on an English, German, Spanish, Italian or Portuguese site. They all go through the translations now, in all six languages. In particular:
- The installation wizard is translated, with a language selector; it initially follows the
browser's language. The command-line installer takes
--lang=en, or the terminal's language. - The marketplace shows each addon's name and description in the site's language.
- Countries (profile, opponents) are displayed in the site's language.
- The bundled roles and email templates are displayed translated; a name changed by the administrator stays as the administrator wrote it.
- Moderation shows a report's status, reason and type, and a sanction's type,
spelled out — it used to display the codes (
pending,forum_message,ban_temp). - A group created in one language no longer disappears when the site is displayed in another — it dropped out of the list, and its permissions with it.
- The administration theme declared itself as written in English: on an English site, its French strings were never translated. This is fixed, as are the module scripts, which looked for their translations in the wrong place.
- The account deletion confirmation asks you to type the word in the site's language ("DELETE" in English), and no longer "SUPPRIMER" everywhere.
A check,
check-textes-en-dur, now rejects any hard-coded interface text in CI. - The installation wizard is translated, with a language selector; it initially follows the
browser's language. The command-line installer takes
-
Visitors' browsers can keep the site's files, if you ask for it (2026-09-22). The manifest already made the site installable; the site can now keep its images, styles and scripts in visitors' browsers. Off by default, to be turned on in Administration → Settings → General Preferences ("Installable app").
Pages, however, are never kept. A page held in memory would survive a new deployment: anyone who had already visited would be served a days-old site, with nothing to signal it. A change therefore remains visible immediately.
Unchecking really uninstalls. This is the tricky part of this technology: a component of this kind, once installed in a browser, stays there even if the file is removed from the server. The switch therefore removes nothing — it serves a version that uninstalls itself on the next visit. That is why the feature is off by default, and why a core update never turns it on by itself.
-
The site can be installed as an app (2026-09-21).
/manifest.webmanifestis generated by the site itself — name, description, theme color, start URL and favicon come from the settings — rather than dropped in as a file, which would be wrong everywhere else. -
Sensitive folders are closed under Apache too (2026-09-21). The package's
.htaccessfiles protected less than the supplied Caddy configuration: under Apache, the installer stayed reachable once the site was installed (only its own check kept it from running), and on a site installed from the git repository,docs/,tools/andtests/were reachable too — the documentation could be read as plain text.cache/,config/,install/,tools/,tests/anddocs/now each have their own guard, the root also denies.neonand.mdfiles, andtools/check-htaccess.phprereads the list on every run, with the reason for each line. -
An unknown address no longer writes an error to the site log (2026-09-21). The
pagesmodule, which receives as a last resort the addresses nothing else recognizes, wrote an error line for each one — every probing bot added some. For addon developers: a controller can now declare a refusal as routine (Module_Checker::refus_ordinaire()); the diagnostic is still shown on screen in debug mode. Thecheck-journalcheck now rejects any line the product itself writes to the error log. -
Two checks enforce the repository's rules (2026-09-21), for contributors.
tools/check-tools.phpverifies that every tool follows the shared rules: descriptive header, reliance on the shared library rather than copied code, standard verdict and exit codes, reserved port, up-to-date catalog.tools/check-docs.phpchecks the documentation: correct figures, valid links and anchors, no orphan document, no mention of a tool that no longer exists, no passage copied from one document to another, documents that stay readable. Both run in continuous integration. -
Eight new addons, all optional (2026-09-20). An update does not install them: on an existing site, they are added from Administration → Themes & addons, and removed the same way. A fresh installation includes them with the Complete profile, offered by default, but not with the other profiles.
Addon What it does Seasonal effect (widget) Snow, confetti or leaves across the whole screen, during a date range. Dates are written MM-DDwithout a year — the season comes back on its own — and can span New Year (from 12-15 to 01-06). Out of season, the page receives nothing at all: no image, no script. The system setting that asks for reduced motion (prefers-reduced-motion) is respected, and the animation pauses when the tab goes into the background.Feed reader (widget) The latest articles from an external RSS or Atom feed. A page almost never waits for a third-party site: it reads a cache, and even serves stale content rather than keep visitors waiting; refreshing happens in the scheduled task. Only the very first display, with an empty cache, waits for the feed, for three seconds at most. A feed that is down is set aside for ten minutes instead of being retried on every visit. Quotes (module) A categorized collection, with author and source. Recipes (module) Ingredients and steps entered one per line, times and number of servings, schema.org/Recipemarkup that search engines read.Glossary (module) A lexicon sorted by letter. "Éclaireur" goes under E, "Æther" under A, "1v1" under #. The letter is computed, never typed in. Places map (module) Places on an OpenStreetMap map, with address, description and link. The map library is hosted by the site; only the background map tiles come from OpenStreetMap. The page stays useful without JavaScript — the list, the addresses and the links are there. Web radio (module) A stream player, what's on air, and the weekly schedule. A show can span midnight: "Saturday 22:00 → 02:00" remains Saturday's show. Sandbox (module) Members only: you try out formatting there and see the site's exact rendering. Above all, it shows what the site removed — it is the only page that answers "why did my table disappear?". The three content modules (quotes, recipes, glossary) share the FAQ's pattern: categories, search, filter, sorting and drafts.
-
Choosing content at installation. The web installer now offers a site profile between the requirements and the database: Complete, Gaming / eSports, Community or Core only. Modules can still be unchecked one by one, and those a module requires are added automatically (awards needs teams) with a message. Without JavaScript, the form still works — the server keeps only what belongs to the chosen profile.
No addon list is written by hand: profiles are built from each addon's
presetsdeclarations. That is the difference from the June 2026 attempt, abandoned because a slimmed-down installation ended in a server error (500): core modules queried the tables of optional modules without checking that they existed. Those checks now exist and are verified on every CI run. -
Every shipped module, widget and theme declares its role: in its
__info(), it states whether it belongs to the core, which installation profiles it is part of, and which modules it needs. Core membership was until now a side effect of being in the package — which is howemojis,filesandwebhooksended up there without any decision being made. -
Three new continuous integration safeguards:
Check What it rejects check-addon-declarations.phpan addon that declares nothing, a nonexistent dependency, a core addon that depends on an optional one, a non-distributable addon published to the catalog, a declaration that disagrees with seed.sqlcheck-addon-coupling.phpa fatal coupling (table, class) neither declared nor annotated; a hard cycle. Read from the code with the PHP tokenizer, not with regular expressions — so no false positives on comments check-install-profiles.phpa profile that does not boot. It really installs on a throwaway database, serves the site and fails on the slightest 5xx — including on the routes of absent modules, which must return a clean 404 -
Finding a member from the search bar. The site search only found content — forum, news, pages. Looking for someone meant opening the member directory and browsing through it, even though it is one of the things people search for most often on a community site. Members now appear in the global search and in the instant suggestions, by username, first name or last name.
Nothing new is exposed along the way: these three fields are already shown on each member's public profile, and the search applies exactly the same filter as the directory — a deleted account does not appear in it.
-
Managing a series of events in one go. Creating a recurring event already generated all its occurrences in one click — going back over them then meant opening them one by one.
Now, an occurrence in a series has a "Delete the whole series" button, whose confirmation states how many occurrences will go, and the edit form offers "Apply to every occurrence in the series". The title, type, descriptions, location, image and publication status are copied to each one; the dates never are — they are what distinguish one session from the next.
-
Rolling back an update, and restoring a backup. The CMS already took a full backup — files and database — just before each core update. It did not know how to use it again: the safety net was in place, but no one knew how to fall into it.
Now, if putting the files in place or a migration fails, the site is automatically put back in the state it was in; the message says both what failed and whether the rollback succeeded. Each backup in the list also has a Restore button, for cases where the damage does not come from an update — a third-party module, an action you regret. The confirmation states what will be lost.
What the restore does not touch, on purpose: the site configuration (putting back outdated database credentials would cut the site off from its own database), the logs (they are the record of the incident being repaired) and the cache (emptied so it gets rebuilt, rather than put back in the state of another version).
Along the way, the backup now includes
vendor/, which the update package ships and which it did not include: without it, a rollback would have put the old code back on top of the new dependencies. -
Replying to a comment. The "Reply" button, disabled in the original code, now works: the reply is shown indented under the comment it answers. Only a top-level comment of the same content that has not been deleted can be answered (one level of replies); the server checks this.
-
For theme creators: zones called by name (idea borrowed from HiddenCMS). A view now calls a zone by its name —
$this->output->region('content')— rather than by its number —zone(2). The theme declares the mapping between names and zones in its__info()(regions). The oldzone()call still works, and a theme without this declaration is unchanged. The five public themes (Nebula, Forge, Granite, Blockcraft, Extend) use the names. -
Installing from the command line (
install/cli.php, idea borrowed from HiddenCMS): an alternative to the web wizard, handy for installing a server in a reproducible or automated way. Its options cover the database (--db-*,--create-db), the administrator account (--admin-*), the site name and address (--site-name,--site-url), the language (--lang), demo data (--demo) and how it runs (--force,--yes,--dry-run,--no-lock). Without options, it asks the questions one by one and hides the password as it is typed;--admin-pass-envreads the password from an environment variable so it does not show in the process list. It uses the same code as the web wizard and installs every shipped addon, like the Complete profile. -
A runner for the whole battery:
php tools/check-all.php. The checks had no common entry point — CI calls them one by one, and locally everyone wrote their own loop by hand, never the same one. The runner discovers the checks present (a new check joins without anyone touching it), runscomposer auditfirst, caps each check's duration, and states at the end what it did not run: the browser tests (--navigateur) and the checks with an explicit target, includingcheck-restauration, which deliberately damages the site. -
Each addon shows what it looks like (2026-09-22). The administration's "Themes & addons" page and the marketplace show a thumbnail: a real screenshot of the module, widget, theme or language as it runs on the demo. All 61 marketplace addons have one, at 960×600 for modules and widgets. To get there, six modules and two widgets were installed on the demo with sample content — a glossary, quotes, recipes, a places map and a web radio schedule — and the widgets that display nothing without settings were captured with sample settings. Whatever has nothing to show keeps its icon, such as the Discord, GitHub and Google connectors, whose icon is their logo. The demo also finally carries its community's identity — name, type, creation date and description — which had remained empty.
-
Ten addons finally have a description (2026-09-22): the six languages and the four external login addons (the base one and its Discord, GitHub and Google connectors). It is shown in the site's language.
-
Profile fields defined by the administrator (2026-09-20). A member's profile only had fixed fields. Administration → Users → Profile fields lets you add your own — in-game name, platform, rank… — from eight field types. Members fill them in the "Additional information" panel of their profile. A field is private by default; once made public, it is shown on the member's page. The values are included in the member's personal data export and are deleted with their account.
-
The site font can be chosen in the administration (2026-09-20). Administration → Settings → General Preferences → Site font offers twelve fonts, which replace the font of every theme. By default, "Theme font" keeps each theme's own font and calls no outside service; the twelve fonts on offer are served by Google Fonts.
-
Sign-in history is no longer kept forever (2026-09-20). It records the IP address, browser and date of every sign-in, and nothing ever deleted it. It is now purged beyond 395 days (thirteen months), a duration set in Administration → Settings → General Preferences; 0 turns the purge off.
-
Reminders for followed calendar events (2026-09-20). A member who follows a calendar event gets a reminder before it starts, like the participants of an event in the Events module. The delay is set in the calendar's configuration ("Reminder before a followed event", 24 hours by default, 0 to disable).
-
The update package states what it protects and what it removes (2026-09-20). It carries an
nf-manifest.jsonfile: the site folders an update never writes (config/,install/,upload/,logs/,backups/,cache/), and the files the version removes. Until now, the update worked out on its own what to delete; a package without this file is still accepted.
Changed
-
The whole interface speaks Bootstrap 5 (2026-09-23). About 470 uses of Bootstrap 3 and 4 classes remained, kept working by a compatibility stylesheet: form fields, color badges, close buttons, full-width buttons, "avatar and text" blocks, input groups, checkboxes, form grids. All the markup is migrated to its Bootstrap 5 equivalents; what is specific to the product now carries a name of the product's own. A check refuses any old name coming back, even redefined. What you can see:
- drop-down lists have their arrow back — about a hundred, in the administration and on the site, looked like text fields;
- color badges (statuses, groups, roles, sanctions) are readable in every theme, in light and dark alike: their text failed the contrast check;
- the "Moderator" and "Senior Moderator" groups are displayed: their name was written in white on a white background;
- dialog close buttons have a large enough click area on phones.
-
JavaScript is reviewed by a linter (2026-09-22). Seventy-one project files written over the years, with nothing checking them beyond their syntax and the absence of jQuery. ESLint sees what syntax does not say: a global variable created by forgetting a
var, a disguisedeval, code after areturn, aninnerHTMLfed from a computed value. No npm package is deployed: this is development tooling, just like PHP static analysis.The rules are chosen on a single criterion — having already bitten here. It is the family of "
$is not defined" that had killed table sorting across the entire administration for three months. The existing mess (innerHTMLuses to sanitize) is frozen as is rather than rewritten all at once: its count can no longer go up. -
The PHPStan baseline is regenerated by one command, and the core
Configclass is annotated (2026-09-21). Three new errors led to reopeningphpstan-baseline.neon: entries added by hand were written differently from PHPStan's own, and nearly three hundred blocks no longer matched anything — an entire "Function NeoFrag not found" family, dead since the bootstrap file declares the function. The configuration lives inphpstan-base.neon,phpstan.neononly adds the baseline to it, andcomposer stan:baselineregenerates it in full.Confignow annotates the current language, the list of languages and the core settings that modules read: these accesses are analyzed instead of being frozen. -
The tools in
tools/rebuilt on a shared library (2026-09-21). About sixty tools, each written its own way, copied the same plumbing: starting a PHP server, opening an administrator session, launching Chrome, readingconfig/db.php. A lesson learned in one did not spread to the others.tools/lib/now carries this foundation — connection, session, server, browser, repository traversal, SQL, options, verdicts — and each tool comes down to its own logic. Fifty-two remain, with no loss of function:check-js-syntaxandcheck-js-jqueryformcheck-js-sources;check-docs-countsandcheck-docs-liensformcheck-docs;check-lang-argsjoinscheck-langs;seed-wiki-docsanddump-wikiformwiki-docs;smoke-testbecomescheck-smoke, like every check;addons-manifestandtable-map, which are data, live in the library. Each tool now has its own reserved port (several were sharing one), three exit codes that distinguish "nothing to report", "rejected" and "could not judge", and a header from which the catalog intools/README.mdis generated.bs5-codemod, the tool from the June Bootstrap 4 → 5 migration, is removed:check-classes-bs4covers everything it checked, including the breakpoints of directional utilities. -
declare(strict_types=1)across the whole useful scope (2026-09-21): 1,365 files versus 78 the day before. The work had been moving forward in small batches since June; it is finished. The 209 remaining files are theviews/**.tpl.phptemplates, where the declaration would be syntactically valid and would protect nothing.The conversion was carried out in order of decreasing risk — the number of native function calls per file — with the test suite run between each batch.
An underlying consequence, specific to this product: PHP in strict mode refuses
__toString()for internal functions. Yetlang()returns a deferred translation object, resolved at render time once the language is finally known, and dates are objects too. The administration bar, each news item's page and several screens therefore crashed with a fatal error — module knocked out, page truncated, without a word. The conversion now happens at the escaping point (htmlspecialchars), that is, where the value must become a string by definition: nearly 500 locations in 86 files.The wave also required eleven explicit conversions, three of them on paths taken on every page — the display order of languages and groups, read when every session starts, and the computation of the token that protects every form against forged requests (CSRF): without them, a site with two active languages would have returned a 500 error on all its pages.
-
The log now says WHERE, not just what (2026-09-21). When a module throws an exception, its rendering is abandoned and the page is displayed truncated — silently. The log line gave the message without ever naming a file. It now names the first location in the product's own code in the call stack. Tracing the origin of the errors above went from an investigation to a simple read.
-
The SCSS compiler moves to 2.1 (2026-09-20). On PHP 8.5, each style compilation wrote ten deprecations to the log; there are none left. 2.x minifies a little more (
.25reminstead of0.25rem,#cccinstead of#CCCCCC), which changes three stylesheets — those of the addons and awards modules and of the maintenance page. The equivalence was not deduced from the text: both sets of CSS were fed to Chrome's engine, which sees exactly the same rules, properties and computed values in them. -
The documentation was reread in full against the code (2026-09-17). The installation, concepts and development guides (creating a module, a widget, a theme, the framework) dated from June and described the CMS as it used to be: an installation with no choice of profile, a theme that loaded jQuery, Bootstrap 4 grids. They now say what the product does — installation profiles, addon declarations, named regions, jQuery-free front end under a strict security policy, widget settings with fallback values, shared color vocabulary. The README, the contribution guide, the security policy and the technical reference are aligned likewise.
-
What an addon allows is read from its declaration, no longer from hard-coded name lists. Three of them decided the fate of addons, and all three were wrong: the widgets list protected seven names that are not widgets; the themes list protected a nonexistent
defaulttheme while leavingnebula, the only public theme in the core, deletable as soon as it was inactive; the modules list duplicated, in disagreement, what__info()already said. Checked addon by addon against the old logic: no change in what can be disabled, no change in active state, and 39 addons become non-deletable — which is the purpose of the fix. -
The Trash no longer knows about other modules. It hard-coded the list of tables, primary keys and restore methods of
news,articles,gallery,commentsandforum: it therefore could not belong to the core without pulling four optional modules along with it —commentsitself is in the core. Each module now declares its restorable types; the core collects them. Same inversion for the content descriptors (reactions, subscriptions, revisions). -
The list of core and optional addons is no longer maintained by hand.
tools/lib/addons-manifest.php, a table written by hand and frozen in June, is now derived from the declarations. The two had already diverged:emojiswas listed as core while it declares itself à la carte — so it could be unchecked at installation but was missing from the catalog, and therefore impossible to reinstall. Catalog: 52 → 53 addons that day (61 when 1.2.0 shipped). -
Installer reworked visually: two-column layout, style tokens taken from
nebula(navy and teal, accent#2dd4bf), gradient moved out from behind the text, logo set in a badge.
Fixed
-
Monitoring backup works again (2026-09-23). Since September 21, it stopped at the very first row of the database: the "Run backup" button, and the core update, which starts with a backup, failed on every site. Found while testing the update before releasing it.
-
Switching languages on a translated news item no longer leads to a not-found page (2026-09-23). The language selector keeps the address, and with it the title in the previous language: the translated version rejected it. It now redirects to its own address.
-
A translated page is displayed in the right language (2026-09-23). Public pages were served in whichever version the database returned first, regardless of the visitor's language.
-
The theme chosen by a visitor no longer spills over onto another site on the same domain (2026-09-23). The choice was remembered for the whole domain: a visitor who switched one site to Forge also saw another site installed under the same domain in Forge. The choice is now remembered for each site separately, and a new setting, General Preferences → Theme choice, lets you turn it off: the menu disappears and the default theme applies to everyone.
-
The demo resets itself again (2026-09-23). An error in its dataset had been making the fifteen-minute reset fail since the day before: whatever visitors changed there stayed. A check now rejects this defect before anything goes live, and a failed reset is written to the log instead of going unnoticed.
-
The demo's emergency account no longer appears anywhere (2026-09-23): not in the member list, not in search, not among online members, not in the administration; its page answers "not found".
-
The site on a phone (2026-09-23), based on screenshots taken on a phone:
- The menu is always reachable. On the Nebula theme, it disappeared below 860 pixels wide with no button to reopen it: a "burger" button now opens it. On the other themes, a menu too long to fit on one line collapses behind a "Menu" button, instead of wrapping until it fills the screen; a short menu stays expanded.
- Stacked blocks no longer touch: the member area was stuck to the block above.
- Text no longer sticks to members' avatars (comments, discussions, author of a news item, applications, profile).
- The slideshow dots are real buttons, centered under the image, and stay light on dark themes, like the arrows and the caption: Bootstrap turned them black.
- The Extend theme's search keeps its button next to the field.
- Photos in an album fill their card.
- The comments header shows the title before the counter, which used to precede it.
- Abbreviated dates are written in the site's language ("30 août 2026", "Sonntag"), and no longer in English; the author line of a news item is back to a normal text size.
-
The gallery photo page exists (2026-09-23). The "Random image" widget, the gallery slideshow and the link of a comment posted on a photo all led to a not-found page: it had never been declared. It shows the photo, its description, the way back to the album and the comments. A photo also follows its album's rules: no more access through its address when the album is in the trash or not yet published.
-
Last remnants of Bootstrap 3 and 4 (2026-09-23), which their class names did not give away: the slideshow structure, the close crosses that displayed a "×" on top of the icon, two accordions (FAQ, navigation links) still built the Bootstrap 4 way (the FAQ one opened, but without the component's look or arrow), fields that wrapped themselves in an empty group, the tooltip arrow left white under a dark tooltip, and two rules of the partners widget.
check-classes-bs4now also reads the STRUCTURE of components and the styles written in views. -
Error pages that weren't (2026-09-23). In moderation, and in the management of roles, email templates, members and the slideshow, an address pointing to a removed item was supposed to answer "page not found": it called a function that does not exist, wrote a warning to the log, and the moderation page displayed empty. Found by teaching static analysis the framework's "magic" functions — which trimmed 455 occurrences from its list of exceptions.
-
The button in emails sent to members is readable (2026-09-23): account validation, lost password and the other templates had a white button on light turquoise (contrast 2.3:1). The fix applies to the shipped templates and to those already installed, except those an administrator has customized.
-
Last display touch-ups (2026-09-23): wiki links in the theme's readable tint; "success" buttons on dark themes; status of an application, whose box had no background (classes from an old administration theme); social networks on member cards; truncated titles in the administration; a visitor who follows the link to take part in an event gets "Unauthorized access" instead of a not-found page.
-
Text is readable everywhere, in light and dark mode alike (2026-09-23). Links and buttons in the administration, alerts, dates and table headers of the themes, news categories, the marketplace page on light themes, the cookie banner's "Accept all" button, reputation and event-type badges: all now reach the minimum recommended contrast (4.5:1). In dark mode, the administration has its own status colors, and the "Site health" panel and the gallery's image upload area follow the theme.
-
The administration on a phone (2026-09-23). The top bar wraps onto two lines — the full breadcrumb, then the buttons — instead of truncating the page title; small targets (home icon, counters) are large enough for a finger; the header of the forum categories no longer slides under its buttons.
-
The forum on a phone (2026-09-23): in a topic, the author moves above the message instead of leaving it only 200 px of width, and a message's buttons no longer cover its date.
-
Translated plurals (2026-09-23). 47 strings had lost their plural form in translation: a site in English displayed "3 topic", "5 image", "2 year". The forum categories counter, written in French in every language, is now translated.
-
The site documentation is up to date (2026-09-23). The ten wiki pages shipped with the product and those of the demo were a week behind the guides; "Create a widget" still taught a Bootstrap 4 class. A check now compares them with the guides.
-
The forum reply form fits on a phone (2026-09-23): the list of allowed attachment types, written without spaces, formed a single word too wide for the screen.
-
The whole site screened, in every theme, every mode and at every width (2026-09-23). An automated check now renders every administration page in its theme and every public page in every public theme, light and dark, logged in as an administrator and as a visitor, at eight widths from 360 to 2560 px, with and without content. This first pass fixed:
- broken icons in the category lists of the gallery, games, teams and news, when a category had no icon;
- the country on profiles in the demo, written out in full: the profile displayed no country and looked for a flag that could not be found;
- email template subjects, which displayed
{{site_name}}verbatim in the list; - tables too wide for a phone (bug tracker, downloads, wiki history, moderation, sessions, event participants, awards): they now scroll within their frame instead of widening the whole page;
- forum lists on a phone: four columns shared 360 px and the last message was cut off; each entry now stacks — icon and title, then statistics and last message;
- the Nebula theme bar on a phone, which overflowed the screen;
- three files requested by pages without existing, and two lines written to the log on every update check as long as no version is published.
-
The "Edit" and "Delete" buttons are no longer staggered (2026-09-22). In the two-column administration lists — news categories, gallery categories… — the second button wrapped onto the next line, offset under the first. They now stay side by side.
-
The monitoring "Storage" card displays correctly (2026-09-22). The gauge was drawn as a full circle instead of the intended half-circle, with a raw number in its center, and the value and percentage, placed at the top, overlapped the stroke; they now sit in the hollow of the gauge. The "Server information" title on the same page wraps onto two lines when the column is narrow, instead of being cut off. In the "Email delivery" group, the "Email transport" line showed
[object Object]instead of the method used. -
A widget placed without settings is displayed with its default values (2026-09-22). When a theme places a widget at installation, or an older layout restores it, it arrives without settings: it then wrote warnings to the log on every page displayed, and the navigation menu simply disappeared. It now receives the values that its form would have saved by default. Widgets already configured do not change.
-
The events page displays again (2026-09-22). It rendered a "not found" error page as soon as it had events to spread over several pages — under a perfectly normal title, which made it hard to notice. The setting for the number of events per page was saved as text, and the code, made stricter the day before, rejected it. All paginated lists now accept this setting in both forms.
-
Poll votes are recorded (2026-09-22). The voter read "Thanks for your vote!", and the vote was never counted: no option was recognized as belonging to the poll. A vote that targets no option of the poll is now rejected with a message saying so, instead of being welcomed.
-
The "Forum" widget finally shows topics and posts (2026-09-22). Its two types, "Latest topics" and "Latest posts", selected no forum category at all, and so stayed empty on every site, however many messages there were.
-
Three other functions fell into the same trap as polls (2026-09-22), found by the check written for this defect:
- opening an application that carries custom fields crashed the page, on the applicant side as well as in the administration;
- a notification that links to a calendar event crashed while building its link;
- the maximum depth of forum replies was never applied: the calculation stopped at the first level.
-
Going back to page 1 of a list no longer leads to a not-found page (2026-09-22). In the administration lists whose number of items per page can be adjusted, the "1" button built an address the site did not recognize.
-
The edit screen for a custom profile field opens (2026-09-22). The screen crashed every time it was opened: it gave no label to its save button.
-
Two "Back" buttons led to a not-found page (2026-09-22): from an image's page, to its album; from a game mode, to its game.
-
"Apply" no longer appears where the application would be refused (2026-09-22): on a closed or full offer, and for anyone not allowed to apply. Likewise, an administrator who reads a staff-only conversation can now invite people to it and report a message in it, instead of being refused after seeing the button.
-
Navigation widget submenus open (2026-09-22). A link that groups other links expanded nothing on click: it still carried the attribute of the old version of Bootstrap, which its own script no longer looked for.
-
The remaining form help tooltips show on hover (2026-09-22). The previous day's fix only covered part of the forms: the (i) icon next to fields on the other administration screens, and label tooltips, still showed nothing — the same attribute defect, in both libraries that build them.
-
Accented menu labels are translated (2026-09-22). A menu link saved from the administration — "Actualités", "Équipes" — is stored in encoded form, and its translation could no longer be found: it stayed in French in the five other languages, with a warning in the log on every page displayed.
-
Marketplace archives are reproducible (2026-09-22). They carried the
.mapsource maps, which the style compiler regenerates on each installation and which the repository ignores: two archives of the same addon, built on two machines, differed even though the code had not changed. Archives no longer carry these files. -
The "Awards" widget can finally be installed by uploading an archive (2026-09-22). It was the only one of the 61 distributable addons not to declare its dependency on the core; the installer, which requires it to recognize an addon, skipped it WITHOUT A WORD — no message, no trace.
-
The maintenance page is no longer blank (2026-09-22). The two fields "Title" and "Content" ship EMPTY, so the page showed NOTHING other than the site name — even though the administration preview promised a title and a text. A default title and message, translated into all six languages, fill the gap; as soon as the fields are filled in, they are what is displayed.
-
Outline buttons have their border back (2026-09-22). The themes redefined only the "primary" outline button variant, and their own rule erased the border of the others: the six other variants used by the product — 90 uses in the code — looked like plain links. The most visible was "Open the website", in the maintenance banner, which only had a border on hover.
-
Technical code is no longer pink in the administration (2026-09-22). The theme only set the font of
<code>snippets, which therefore kept Bootstrap's pink — a color foreign to every palette in the product. Very visible on the roles list. -
The "Close" button of windows finally closes the window (2026-09-22). In ALL the product's windows, the "Close" (or "Cancel") button at the bottom did nothing: you needed the cross in the top right corner. The cause: Bootstrap 5 renamed the attribute that triggers closing, and the core still set the old name; a browser ignores an unknown attribute silently. The button has also become a real button — it was rendered as a
<span>, and so unreachable by keyboard. Two safeguards keep it from coming back: the code check rejects the old name, and a browser run, launched by hand, CLICKS the button. -
The Nebula theme no longer shows its menu twice (2026-09-22). Every fresh installation showed the site name twice and the menu twice: the theme draws its own bar, and the installation ALSO placed a title and a menu right below it. The menu now lives in the top bar, where it appears only once — and it is configurable from the administration, which was not the case before: the bar's links were hard-coded, so "Contact" was missing and a disabled module kept its link. Sites already installed are fixed by a migration, which only touches the header if it has remained as shipped.
-
Switching languages no longer lands on a page of code (2026-09-22). The footer language selector — the one in five themes, including the default — sent the visitor to a page displaying
{"redirect":"/en/…"}as raw text, instead of taking them to the translated page. You had to go back to get out of it.The cause: this selector is a real form, which nothing intercepted, and it targeted a technical address that always answers in JSON. The server now distinguishes a call from the "Choose my language" window, which expects JSON, from an ordinary form submission, which expects to be taken to the page. Found by following the gesture in a real browser, which no check had done until then.
-
Content written in a single language no longer sends the other languages to an error page (2026-09-21). A news item written in French still offered the five other languages in its selector, and all five returned 404: measured on the demo, 60 dead addresses out of 72. A foreign visitor's most natural gesture landed on an error. The version that exists is now served, with a banner saying so in the visitor's language and a link to the original. This applies to news, articles and albums (and their categories), as well as teams.
Two inseparable precautions, without which the cure would cost more than the disease: the page now announces to search engines only the languages that really exist, and it designates the original as the canonical address — otherwise six addresses would be indexed for a single text. In the administration, no fallback: an empty version must show as empty, that is what one comes to fill in.
tools/check-langues-contenu.phpenforces all three properties, in continuous integration. -
Continuous integration is green again (2026-09-21); on the main branch, it had been red since June 10. Almost all the causes lay in the test tools and the CI configuration, not in the product. The exception: under PHP 8.3's built-in server, the site could miscompute its base address and redirect stylesheets and scripts; it now only derives it from a
SCRIPT_NAMEending inindex.php. A test suite that cannot run now fails instead of silently skipping itself, and most CI jobs have a time limit. -
The installer logo did not display on a fresh installation (2026-09-21): the path was relative to the
install/folder, which the server does not serve under that address. The image is now embedded in the page. -
Components that no longer opened since the switch to Bootstrap 5 (2026-09-21). Bootstrap 5 prefixed all its data attributes with
bs-. An attribute left under the old name causes no error: the component simply does nothing. These were silently dead:What no longer worked Where The FAQ accordion did not open modules/faqAn event's participant list did not expand modules/eventsHelp popovers on part of the forms were empty core — neofrag/libraries/form.phpThe forum profile popover, the one for deleting a navigation link modules/forum,widgets/navigationTooltip placement in the live editor, the carousel delay modules/live_editor,widgets/slider -
Broken layouts, silent ones too (2026-09-21). Bootstrap 5 renamed its directional utilities; a renamed class is no longer defined anywhere, and the element keeps its default layout without anything flagging it.
- Panel and modal footers did not align right. The core built the class
by concatenation (
'float-'.$align), so no search forfloat-rightcould find it. The product's only compatibility shim covered just the administration theme's tables: nothing for the public themes. Same cause for table cell alignment, in both libraries. - No rejected form field was marked, on any theme. Both libraries
set a legacy name —
has-error(Bootstrap 3) andhas-danger(a Bootstrap 4 pre-release) — that no served stylesheet defines. The message did display, but nothing indicated the field concerned. They are replaced by an in-house marker, defined once. - The footer carried
<div class="float-right">: "Powered by NeoFrag Reborn" dropped onto a new line, on the left. The value goes back a long way — an Alpha 0.2.2 migration had changed it frompull-righttofloat-rightat the time of Bootstrap 4, and the next step was never taken. A migration (2026_09_20_bootstrap5_float) fixes existing sites; the seed now shipsfloat-end. - And the last dead classes in views: the debug bar on a phone, the gutter of event lists, the menu editor labels, the two bars of the recruitment vote — which came out in the same color, the chart no longer distinguishing favorable opinions from unfavorable ones.
- Panel and modal footers did not align right. The core built the class
by concatenation (
-
An article's body could be wiped out on display (2026-09-21). Building the table of contents replaced the text with the result of a processing step that can give up on a long article: the page then answered normally, with an empty article, and nothing in the logs.
-
A widget placed at the top or bottom of the page disappeared. The Nebula theme offered the "Header" and "Footer" slots in the layout editor, but did not display them. The widget was indeed saved, and nothing appeared — without a message. The four other public themes were not affected.
-
The navigation widget offered two internal modules (
live_editorandpages) it should have hidden. The same defect made the administration statistics lose their module name, with no visible effect as long as two modules do not use the same statistic name. -
The debug log grew without limit when enabled. It is now capped: beyond 64 MB, it starts over while keeping the previous generation.
-
On an English site, "No" stayed in French ("Non").
-
Five admin pages offered no way back (2026-09-20): a member's detail page, the audit log, adding a group, the session list and the monitoring files. The breadcrumb only makes the module name clickable if the module declares that it has an admin home page;
userandmonitoringdeclared the opposite even though they have one. Once on these pages, you had to go through the side menu or the browser's back button. -
Search engines received JSON instead of the sitemap. Requested at the root, without a language prefix — the only way a robot requests them —,
/sitemap.xmlresponded{"redirect":"/fr/sitemap.xml"}with a 200 status code, androbots.txtandhumans.txtdid the same. The redirect that adds the language prefix responded in JSON whenever the URL ended in.txt,.xmlor.json. Since these files have no per-language version, they no longer go through this redirect: they are served directly, with their actual content type. -
/favicon.icoreturned 404. Browsers request this URL whatever the page declares; it now leads to the favicon configured in the settings, or to the CMS's own. -
Enabling two-factor authentication crashed. When the recovery codes were being saved, an error interrupted the page: the account ended up marked "protected" without a single backup code. Found by a test written while tightening the typing of this library; fixed, and the test stays.
-
A "single attempt" limit never blocked. The threshold was only checked starting from the second attempt. No effect on the shipped thresholds, but the moderation's report limit, which can be set as low as 1, then never blocked. The threshold now applies from the first attempt.
-
Some text stayed in French in the other languages, and the log kept filling up. Three causes, all fixed: the title of a wiki page, an event type or a donation campaign went through translation as if it were interface text (it has no translation: it is content); text that was already translated went through translation again — "Espace membre" became "Member area", then "Member area" was looked up as a key and reported missing on every display; and thirty interface strings (the "Username or email address" field on the login form, panel titles in the games and recruitment admin, the profile's social networks…) had no translation in any language, because the check that verifies the six languages only looked at explicit translation calls, not at field and header titles translated further down the line. The check now sees them; the thirty strings are translated into English, German, Spanish, Italian and Portuguese.
-
Google Analytics now waits for your consent. If an Analytics ID is configured, Google's script now only loads after a click on "Accept all" in the cookie banner, never before. In addition, the site's security policy blocked this script in every case: Analytics was not working, and nothing said so. It works now, and only with the visitor's consent.
-
Three admin features had stopped responding since June. Sorting tables by clicking a column header, drag-and-drop to reorder categories, forums and subforums, and the Open/Closed buttons on the Maintenance and Registrations pages: their scripts still assumed jQuery was present, although it was removed from the CMS in June 2026, and stopped dead when the page loaded — with no message, the buttons simply remaining inert. All three have been rewritten without jQuery, are covered by browser tests, and an automated check now rejects any script that would call on jQuery again.
-
A malformed URL caused a server error. A request such as
/fr/a:80or/:80, the kind robots send, crashed the computation of the URL's extension and returned a 500 error to the visitor. The site now responds with an ordinary 404. -
The contact form wrote "From:" with the visitor's address. Sending a message from your server on behalf of
visiteur@example.orgis exactly what mail providers' anti-spoofing protections (SPF, DMARC) reject: the message ended up in spam or was refused. The sender is now the site's contact address, and the visitor's address is placed in "Reply-To", which amounts to the same thing in practice. Same fix for replies to an application, which could go out with the administrator's personal address. -
The installer could save an invalid contact address. When the site was installed by accessing it through its IP address, the contact address became
noreply@<IP>, which mail providers reject — and no email went out anymore, without any visible error message. The address is now only derived from a real domain name. -
An empty banner appeared on pages where a layout zone is empty. In the Extend theme, every page other than the home page showed a wide colored band containing nothing, as well as an empty section above the content.
The cause was not the theme: a zone that was declared but had no widget returned whitespace rather than nothing at all, so themes drew their frame around nothing. Fixed once for all themes. If you build a theme, you can write
if ($zone = $this->output->region('banner'))(orzone(n)): the condition now tells the truth. -
The Extend theme displayed its "Powered by NeoFrag Reborn" credit twice. Its shipped layout placed a block in the footer repeating what the theme already writes itself. Sites already installed are fixed automatically on update; new ones will never have it.
-
Searching for a common word returned an error page. A search such as "le" or "de" did display its results on screen, but the server responded 404 — which is what search engines, monitoring tools and browsers see, and which made a perfectly valid page look like one that had disappeared.
Highlighting the matched words assumed that the searched word necessarily appeared in the displayed text. That is wrong twice over: the search examines several fields and only displays one — a forum topic titled "Salut tout le monde !" matches "le" through its title, not through the message shown below it — and the database compares without regard to accents, whereas the code did take them into account: "éléphant" did come up for the search "ele", without any word being highlighted in it.
Both cases are handled: highlighting now recognizes the accented variants of a letter, as the database does, and when the word really is not in the displayed text, the excerpt simply starts at its beginning.
-
Automatic core updates could not work — five defects, each one enough on its own: the address to check for new versions was set nowhere, so the button never appeared; a global switch, to be set in the configuration, blocked the operation; the download pointed at the original NeoFrag releases, which would have overwritten NeoFrag Reborn; the package stored its files in a subfolder, so an update would have replaced nothing, without the slightest error; and the archive was extracted through a PHP interface deprecated since PHP 8.0.
The switch is replaced by four guarantees: the version source must be on an allowlist of addresses (the same as for the marketplace); the version file only gives a file name, never an address; the package's SHA-256 checksum is verified before a single file is touched; every file in the archive is checked (no path leading outside the site, no symbolic link). The
config/andinstall/folders are never rewritten if they already exist. -
The marketplace catalog was unreachable from any site. The server that distributes it returned
{"redirect":"/fr/…"}with a 200 status code instead of the static file — a body perfectly valid as JSON, and perfectly wrong, hence a silent failure that made every site fall back on its local catalog. The cause was that server's configuration, now fixed: nothing to do on the sites. In the code, the core update now validates the shape of its manifests, no longer just whether they parse as JSON. -
A theme's "Delete" button, in "Themes & addons", only protected the administration theme, by its hard-coded name. It now follows the theme's declaration, and so also protects Nebula.
-
Core-to-optional couplings. Seven modules queried the tables of optional modules: five core ones (
admin,moderation,settings,statistics,user), plusgamesandteams. Two queries were unprotected: inteams, the existence guard forrecruitswas placed after the query it was supposed to protect, and ingames, deleting a game queried the teams table without checking that it exists. The others were guarded but silent: they now carry an annotation verified by the CI. The package's only hard cycle (games↔teams) is broken. -
Live Editor: changes appear without reloading the page. Deleting a widget left it on screen and adding a row did not show it until the page was reloaded, even though the server had saved the change. The script expected a different response format from the one the server sends; the defect dated from the removal of jQuery.
-
Live Editor: dragging and dropping widgets works again. With Columns mode on, an added widget stayed stuck at the bottom of its column and could not be moved above the page content. Rows and columns were not affected.
-
Live Editor: widgets without settings can be added again. Ten widgets that have no settings screen (
breadcrumb,copyright,downloads,forum,module,news,newsletter,slider,surveys,teams) could not be added: adding them failed with a 404 error and no useful message. -
A server error is no longer displayed as content. When an action performed without reloading the page got an error from the server (a 404 page, for instance), some screens inserted that error page into the interface. The error is now treated as a failure.
-
Live Editor: the settings form always matches the chosen widget. When switching widget or type quickly, the slowest response could display another widget's form. The outdated request is now canceled, and the form is no longer reloaded (nor the input in progress lost) when the selection has not changed.
-
The database port is now honored. The CMS always connected on port 3306, whatever port was entered in the installation wizard: it could not be installed or run on a MySQL or MariaDB server listening elsewhere. The port is now saved in
config/db.php(only if it differs from 3306) and used for every connection. -
An activated theme is no longer empty. Activating Forge, Granite, Blockcraft or Extend switched the theme without applying its default layout, which only applied through the "Reinstall to defaults" button: the site showed no widgets at all. Activation now applies the theme's default layout if it has none yet, never overwriting a customized layout.
-
Changing the default theme now also applies to visitors who had picked one. The footer theme selector remembered a visitor's choice for a year, and that choice overrode the default theme: when the administrator changed theme, visitors, and the administrator too, stayed on the old one, with no way out from a theme without a selector. A choice made before the last change of default theme is now forgotten; a choice made after it is still respected.
-
The browser no longer keeps stale copies of pages. Lacking any instruction, it could redisplay an old version of a page: a theme change, for instance, only showed after a forced reload (Ctrl+F5). The site's pages and responses now forbid this caching (
Cache-Control: no-store); images, styles and scripts, served directly by the web server, are not affected.
Security
-
A stored value can no longer inject tags into a form (2026-09-23). The edit form placed a field's value in the page by escaping quotation marks the way a PHP string would — which has no effect in HTML. A value that had not gone through the form itself (import, demo data, another module) could therefore break out of its field and add code to the admin screen: a quote whose source was
"><img src=x>demonstrated it. Text fields, text areas, lists, checkboxes and radio buttons now encode their value; the rendering of a normally entered value does not change. Notifications ("Quote updated", etc.) are also passed to the page's script in a form that can no longer break it. -
The security policy now declares a rule for media (
media-src). It had none and fell back on the general rule (default-src 'self'). It now allows media from the site itself and media embedded in the page ('self' data:); a webradio stream's address is added only if a stream is configured: a site without webradio allows no additional outside origin. -
Eleven security advisories closed on dependencies (2026-09-20):
league/commonmarkgoes from 2.8.2 to 2.10.1 (ten advisories, eight of them severe) andphpseclibfrom 3.0.52 to 3.0.57 (one medium-severity advisory).Nine of the ten commonmark advisories are denials of service through crafted Markdown — quadratic-time parsing, headings with colliding anchors, duplicate footnotes, adjacent attribute blocks, deeply nested XML output — and the tenth is an XSS vulnerability: the filter on
on*attributes could be bypassed with a U+000C form feed. The library renders the Markdown of the wiki, articles and FAQ, written by members. Several of these advisories target extensions the product does not enable (footnotes, attributes, heading anchors, XML output); the update closes them all.Rendering does not change, and that is not just an impression: twelve representative cases — headings, table, task list, code block, injected HTML,
javascript:link, blockquote, strikethrough text, autolink, nested lists, image, entities and footnote, deep nesting — rendered by both versions with the product's actual configuration produce the same HTML byte for byte (1,568 bytes, same hash).Note: Composer now refuses to install 2.8.2, precisely because of these ten advisories. The policy had to be disabled in a throwaway folder to carry out the comparison.