Published 16 days ago
Changelog - Flatboard 5.0.4
Release date: February 19, 2026
New Features
Mastodon Share Button with Instance Picker
- Description: A dedicated Mastodon share button has been added to the social share panel in post threads. Since Mastodon is a decentralized network, clicking the button opens an inline popover letting each user type their own instance (e.g.
pouet.chapril.org,mastodon.social). The instance is persisted inlocalStorageand pre-filled on subsequent uses. - Files:
app/Views/components/share-buttons.phpthemes/premium/views/components/share-buttons.php
Portuguese (pt) Language Pack
- Added full Portuguese language support. Special thanks to Thalles Lázaro for contributing this translation.
- Files:
languages/pt/main.json,languages/pt/admin.json,languages/pt/auth.json,languages/pt/emails.json,languages/pt/errors.json,languages/pt/install.json
Improvements
Share Buttons Extracted into a Dedicated Component
- The social share buttons previously duplicated inline in both
post-thread.phpviews have been extracted into a standaloneshare-buttons.phpcomponent included viainclude __DIR__ . '/share-buttons.php'. Any future change (add/remove a network, restyling) now only requires editing a single file. - Files:
app/Views/components/post-thread.phpthemes/premium/views/components/post-thread.phpapp/Views/components/share-buttons.phpthemes/premium/views/components/share-buttons.php
Share Buttons Display Icons Only with Bootstrap Tooltips
- Share buttons now show only the network icon. The network name appears as a Bootstrap tooltip on hover (
data-bs-toggle="tooltip" data-bs-placement="top"), resulting in a more compact UI. - Files:
app/Views/components/share-buttons.php,themes/premium/views/components/share-buttons.php
Share Buttons Layout Switched to CSS Grid (4 columns)
- Replaced
d-flex flex-wrapwithdisplay:grid; grid-template-columns:repeat(4,1fr)so all 8 buttons fill two even rows of 4 with no wasted space. - Files:
app/Views/components/share-buttons.php,themes/premium/views/components/share-buttons.php
Removed Duplicate "Copy Link" Button from Share Panel
- The "Copy link" button inside the share dropdown was redundant with the copy button already present in the direct link input field above it. The duplicate has been removed.
- Files:
app/Views/components/share-buttons.php,themes/premium/views/components/share-buttons.php
Share Panel Translation Keys Completed (FR / EN / DE / ZH / PT)
- Several translation keys used in the share component were missing or relied on empty key lookups. The following keys have been added to all five language files:
| Key | FR | EN | DE | ZH | PT |
|---|---|---|---|---|---|
discussion.share.ariaLabel | Partager sur les réseaux sociaux | Share on social networks | In sozialen Netzwerken teilen | 分享到社交网络 | Compartilhar nas redes sociais |
discussion.share.viaEmail | Partager par email | Share by email | Per E-Mail teilen | 通过邮件分享 | Compartilhar por e-mail |
discussion.share.mastodonInstanceLabel | Votre instance Mastodon | Your Mastodon instance | Ihre Mastodon-Instanz | 您的 Mastodon 实例 | Sua instância Mastodon |
discussion.share.mastodonInstancePlaceholder | ex : mastodon.social | e.g. mastodon.social | z.B. mastodon.social | 例如:mastodon.social | ex: mastodon.social |
discussion.share.mastodonInstanceSave | Enregistrer | Save | Speichern | 确认 | Confirmar |
discussion.share.mastodonInstanceSaved | Instance enregistrée | Instance saved | Instanz gespeichert | 实例已保存 | Instância salva |
- Files:
languages/fr/main.json,languages/en/main.json,languages/de/main.json,languages/zh/main.json,languages/pt/main.json
Fixed Malformed JSON in Chinese Language File
languages/zh/main.jsoncontained multiple syntax errors (trailing commas inside objects, a raw\n-escaped string appended at the end of the file) that made the file unparseable. The file has been fully rebuilt from its original content with all keys preserved.- File:
languages/zh/main.json
Changelog - Flatboard 5.0.3
Release date: February 17, 2026
Bug Fixes
Duplicate Posts When Using "Load More" in Discussions
- Issue: Clicking "Load More" in a discussion duplicated posts already visible on the page. The best answer post was particularly noticeable as it appeared both in its chronological position and again at the top of the loaded batch.
- Root cause:
LoadMoreManagerinload-more-manager.jsconverted the initialdata-offsetto a page number (offset / perPage), then back to an offset (currentPage * perPage). When the initial offset (e.g. 10) was smaller thanperPage(20), the integer division truncated to 0, so the first "Load More" click requestedoffset=0— re-fetching the first page. - Fix:
load-more-manager.jsnow tracks acurrentOffsetproperty directly instead of converting through page numbers. The offset is incremented by the actual number of items loaded after each request.- Added HTML-level deduplication in
appendHtml(): before inserting elements, existing IDs in the container are collected into a Set and duplicates are filtered out. - Added post ID deduplication in
loadAllPostsForScrubber()to prevent duplicates when the scrubber bulk-loads posts that were already fetched via "Load More". - After
loadAllPostsForScrubber()completes, theLoadMoreManager.currentOffsetis synchronized so subsequent "Load More" clicks don't re-fetch already-loaded posts. - Added post ID deduplication in
JsonStorage::getPostsByDiscussion()as an additional safety net against duplicate files.
- Files:
themes/assets/js/frontend/modules/load-more-manager.js,app/Views/discussions/show.php,themes/premium/views/discussions/show.php,app/Storage/JsonStorage.php
Post Scrubber Navigation Failing Beyond First Page
- Issue: The post scrubber (sidebar navigation widget) could not navigate to posts beyond the initially loaded page. Clicking "next", "last", dragging the handle, or clicking the track to jump to a distant post silently failed.
- Root cause:
loadUntilPostAvailable()relied on programmatically clicking the "Load More" button in a retry loop with 300ms delays. This was fragile due toLoadMoreManager's async timing, and before the offset fix, each click returned the same first page — so the function never found new posts and gave up. - Fix: Replaced the
btn.click()retry loop with a direct call toloadAllPostsForScrubber(), which makes a correct API request withoffset/limitand handles DOM insertion. The function now uses Promise-based flow instead of nestedsetTimeoutchains. - Files:
app/Views/discussions/show.php,themes/premium/views/discussions/show.php
Pagination Rate Limit Error on Discussions with Many Posts
- Issue: Scrolling through a discussion with many posts triggered "Trop de requêtes" (HTTP 429) after ~10 "Load More" clicks, blocking further loading for 5 minutes.
- Root cause: Three compounding issues:
PostApiController::loadPosts()loaded all posts from storage on every API call, then sliced in PHP witharray_slice. Each pagination request re-read the entire discussion.PostApiController::getPostsOrder()called$this->authenticate()on every request to check user sort preferences. This incremented theapi_authrate limit counter (10 attempts / 15 min), even for anonymous users with no credentials.ApiController::authenticate()counted every call as an authentication attempt, including requests with noAuthorizationheader and no session — treating normal browsing as brute-force attempts.
- Fix:
loadPosts()now passesoffset/limitdirectly toPost::byDiscussion(), loading only the 20 needed posts per request.getPostsOrder()checks the session directly instead of calling the rate-limitedauthenticate().authenticate()returnsnullimmediately when no credentials are provided, without touching the rate limiter. Rate limiting only applies when anAuthorizationheader is present (actual auth attempt).
- Files:
app/Controllers/Api/PostApiController.php,app/Controllers/Api/ApiController.php
Missing HTTP 429 Handling in Frontend Pagination
- Issue: When the API returned a 429 rate limit response, the JavaScript
fetchcalls ininfinite-scroll.jsdid not checkresponse.okand tried to parse the error as JSON, causing silent failures with a stuck loading spinner. - Fix: Added proper
response.status === 429detection in bothinitLoadMorePosts()andloadPostsDirectly(), displaying a user-friendly toast notification with the retry delay. - File:
themes/assets/js/frontend/components/infinite-scroll.js
Smart Preloading Triggering Unnecessary API Requests
- Issue: The
IntersectionObserver-based preloading ininitSmartPreloading()fired a real APIfetchrequest when the "Load More" button approached the viewport, counting against the rate limit before the user even clicked. - Fix: Disabled preloading for post pagination. Discussion and pagination link
<link rel="prefetch">are unaffected as they don't hit the API. - File:
themes/assets/js/frontend/components/infinite-scroll.js
EasyMDE Plugin Configuration Options Not Working
- Issue: The "Show line numbers" and "Show status bar" options in the EasyMDE plugin settings did not work correctly. Even when enabled in admin settings, line numbers would not appear in the editor, and the status bar (lines/words/cursor) would remain visible even when disabled.
- Root cause: Two separate bugs affecting different configuration options:
lineNumbers: InEasyMDEPlugin.php(line 413-414), the boolean conversion logic did not handle different value types correctly ("1","0",true,false). The null coalescing operator??only checks fornull, notfalse, causing the initial correct value to be overwritten with incorrect logic.status: EasyMDE displays the status bar by default if thestatusproperty is not explicitly set tofalse. While the PHP code correctly setstatus: false, the CSS did not force hiding of the element.
- Fix:
lineNumbers: Replaced the weak boolean conversion with explicit type checking that verifies if the value istrue,"1", or1using strict comparison operators (===).status: Added CSS rules ineasymde-custom.dev.cssto force hiding of the status bar when empty or disabled usingdisplay: none !importantand related properties.
- Files:
plugins/EasyMDE/EasyMDEPlugin.php(lines 410-424),plugins/EasyMDE/dist/easymde-custom.dev.css(lines 336-362)
Maintenance Mode Not Activating When Enabled
- Issue: When enabling maintenance mode in the admin panel, the forum remained accessible instead of showing the maintenance page.
- Root cause: The maintenance mode check uses a cached value (
maintenance_mode_status) with a 60-second TTL. When the configuration was updated via the admin panel, the cache was not invalidated, causing the old value (false) to continue being used until it expired. - Fix: Added cache invalidation in
Config::set()whenmaintenance_modeis updated. TheinvalidateMaintenanceCache()method now deletes themaintenance_mode_statuscache key whenever the configuration changes. - File:
app/Core/Config.php
Improvements
Infinite Scroll Spinner Always Visible Between Posts
- Issue: In infinite scroll mode, a loading spinner was permanently displayed between the initially loaded posts and any subsequently loaded posts, even when no loading was in progress.
- Root cause: The
.pagination-spinnerdiv inshow.phpwas rendered without thed-noneclass, making it always visible. Theload-more-manager.js(which handles pagination) had no logic to show/hide it, andpagination-handler.jsused a mismatched data attribute (data-pagination-typevsdata-pagination-mode), so its show/hide logic never applied. - Fix: Added
d-noneclass to the spinner by default so it's hidden on page load. Added show/hide logic inLoadMoreManager.showLoading()andhideLoading()to toggle the spinner when loading is in progress. - Files:
app/Views/discussions/show.php,app/Views/discussions/index.php,app/Views/discussions/search.php,app/Views/discussions/tag.php,themes/premium/views/discussions/show.php,themes/premium/views/discussions/index.php,themes/premium/views/discussions/search.php,themes/premium/views/discussions/tag.php,themes/assets/js/frontend/modules/load-more-manager.js
Reduced Log Verbosity in Production
- Issue: Every HTTP request generated ~15 INFO-level log entries (11 plugin loads, plugin system init, auto-enable, permissions init, theme CSS generation, polls storage detection), flooding log files even with debug mode disabled.
- Fix: Changed all plugin loading, initialization, and routine operational logs from
Logger::info()toLogger::debug(). These logs are now only visible when debug mode is enabled. - Files:
app/Core/Plugin.php,plugins/polls/PollsPlugin.php,app/Helpers/PermissionHelper.php,themes/premium/views/components/theme-colors.php,app/Views/components/theme-colors.php
Changelog - Flatboard 5.0.2
Release date: February 16, 2026
Bug Fixes
Category Visibility Cache Mismatch
- Issue: Categories with group-based access restrictions were not displayed to authorized users. Editing any category temporarily restored visibility, pointing to a cache invalidation problem.
- Root cause:
Category::getVisible()generated the cache key before resolving the actual user ID from the session. All users shared the samecategories:visible:guestcache entry, resulting in incorrect permission filtering. - Fix: User ID resolution is now performed before cache key generation, ensuring each user gets their own correctly filtered cache entry.
- File:
app/Models/Category.php
Captcha Turnstile Verification Failure (cURL SSL Error)
- Issue: Cloudflare Turnstile widget showed success, but server-side token verification failed with "Impossible de vérifier le captcha".
- Root cause: cURL error #77 — the CA certificate bundle path pointed to a non-existent macOS Homebrew path (
/usr/local/etc/openssl@1.1/cert.pem) on Linux systems. - Fix: Added automatic CA bundle detection for multiple Linux distributions (Debian/Ubuntu, RedHat/CentOS, OpenSUSE, FreeBSD, Fedora) with
CURLOPT_CAINFO. Addedfile_get_contentsfallback when cURL is unavailable or fails. - File:
plugins/Captcha/Cd/99-seedforge-demo-data-generatoraptchaPlugin.php
Untranslated Checkbox Descriptions in Admin
- Issue: Checkbox fields in Theme and Captcha plugin settings displayed raw translation keys like
form_config.fields.xx_xx.descriptioninstead of translated text. - Root cause: Previous checkbox optimization removed description entries from language files, but the
descriptionkeys still existed intheme.jsonandplugin.json, pointing to now-missing translation keys. - Fix: Removed orphaned
descriptionkeys from 18 checkbox fields inthemes/premium/theme.jsonand 7 checkbox fields inplugins/Captcha/plugin.json. - Files:
themes/premium/theme.json,plugins/Captcha/plugin.json
EasyPages Admin List Crash with Multilingual Labels
- Issue: After saving a page with multilingual menu labels, the admin pages list (
/admin/pages) crashed withhtmlspecialchars(): Argument #1 must be of type string, array given. - Fix: Added
resolveMenuLabel()helper in the admin view to safely resolve multilingual labels before rendering. - File:
plugins/EasyPages/views/admin.php
New Features
EasyPages Multilingual Menu Labels
- Description: EasyPages menu labels (
menu.labelandmenu.group_label) can now be translated per language. The navbar items automatically switch language when the user changes the forum language. - How it works:
- Labels are stored as objects (
{"fr": "À propos", "en": "About", "de": "Über uns"}) in the page JSON data. - The edit form (Menu tab) displays one input field per available forum language (FR, EN, DE, ZH...).
resolveLabel()resolves the correct translation at render time: current language → English fallback → first available language → page title fallback.
- Labels are stored as objects (
- Backward compatible: Existing pages with string labels continue to work. On next save, they are automatically converted to the multilingual format.
- Files:
plugins/EasyPages/EasyPagesPlugin.php—resolveLabel()method + updatedaddNavigationItems()plugins/EasyPages/views/edit.php— per-language input fields formenu_labelandmenu_group_labelplugins/EasyPages/EasyPagesController.php—store()andupdate()process labels as arraysplugins/EasyPages/views/admin.php—resolveMenuLabel()helper for admin list displayplugins/EasyPages/langs/{fr,en,de}.json— new translation keysmenu_label_translations,menu_group_label_translations
Font Awesome 6 Icon Validation
- Description: The icon input fields in the category backend now accept Font Awesome 6 class names (
fa-solid fa-gem,fa-regular fa-heart, etc.) in addition to the legacy FA5 format (fas fa-gem). - Supported prefixes:
fas,far,fab,fal,fad,fa-solid,fa-regular,fa-brands,fa-light,fa-duotone,fa-thin - Files:
app/Controllers/Admin/CategoryController.php— server-side validation (create + update)themes/assets/js/admin/modules/categories-management.js— client-side JS validationapp/Views/admin/categories.php— HTML pattern attributeplugins/EasyPages/EasyPagesController.php— icon validation in store/update
Improvements
Captcha Plugin Debug Logging
- Added
debugLog()method for troubleshooting captcha verification issues. - Comprehensive logging throughout the verification chain: token extraction, API call, response parsing.
- Controlled by
debug_modetoggle in plugin settings. Logs written toplugins/Captcha/logs/captcha_debug.log. - File:
plugins/Captcha/CaptchaPlugin.php
Captcha Plugin HTTP Resilience
- New
httpPost()method with layered fallback strategy: cURL (primary) →file_get_contents(fallback). - Proper
Content-Typeheader and cURL error detection withcurl_error()/curl_errno(). - Cross-platform CA bundle auto-detection for SSL verification.
- File:
plugins/Captcha/CaptchaPlugin.php
Configurable Pagination Mode (Classic / Load More / Infinite Scroll)
- The admin
pagination_typesetting now controls frontend pagination across all views (discussions index, discussion posts, tag pages, search results). - Classic mode: Displays numbered page navigation via the
pagination.phpcomponent. Full page reload on navigation. - Load More mode: Displays a "Load More" button that fetches additional items via API (offset-based JSON) without page reload.
- Infinite Scroll mode: Automatically loads more content when the user scrolls near the bottom of the page. The button is hidden and replaced by a spinner indicator.
- Admin views always use classic pagination regardless of the frontend setting.
- Controllers:
app/Controllers/Discussion/DiscussionController.php—index()andshow()now pass$currentPage,$totalPages,$totalto views; added?page=Xquery parameter support for classic mode.app/Controllers/Discussion/CategoryController.php— Same pagination variables and?page=Xsupport added.app/Controllers/Discussion/TagController.php— Same pagination variables and?page=Xsupport added.
- Frontend views (conditional rendering):
app/Views/discussions/{index,show,tag,search}.php— Render pagination component, load-more button, or infinite-scroll spinner based onPaginationHelper::getConfiguredType().themes/premium/views/discussions/{index,show,tag,search}.php— Same conditional pattern applied to premium theme.
- Admin views (forced classic):
app/Views/admin/{plugins,themes,users,reports,bans,audit-logs,backups,webhooks-history}.php— Force$paginationType = 'classic'before includingpagination.php.
- JavaScript:
themes/assets/js/frontend/modules/load-more-manager.js— Readsdata-pagination-modeattribute to activate infinite scroll; fixed tags type to useoffset/limitAPI params; addedformat=htmlto all API requests; added tag button initialization.themes/assets/js/frontend/components/infinite-scroll.js— AddedisManagedByLoadMoreManager()guard to prevent duplicate event handlers when both scripts are loaded.
🐛 Found a Bug?
Please report any issues in the support forum. We're committed to maintaining the quality and stability of Flatboard.
Happy posting! 🎊
The Flatboard Team
- flatboard pro flatboard release new
- Applaud(1)
Thalles