1.5.2
Released 2026-06-13
Added
The admin Impact Report can now calculate a real, defensible SROI ratio. Until now the page's "SROI Ratio" was simply the configured multiplier echoed back (×3.5 always showed "3.5:1") — not a return on investment at all. The page now has a proper Social Return on Investment section following the international SROI methodology used in the 2023 Timebank Ireland study: you enter your total investment and your verified outcomes (each valued with a financial proxy, e.g. from the HACT Social Value Bank), and the platform applies the standard deductions for deadweight, displacement and attribution, projects future years with drop-off and discounting, and divides by your investment. Every coefficient used is shown alongside the result so the figure is auditable. A one-click template pre-loads the four Timebank Ireland outcome categories with their calibrated proxy values. The calculation engine reproduces the published TBI study to the euro (€50,000 in → €803,184 of social value → 16.06:1), and that check is now a permanent automated test. The old hours-based figures remain, relabelled honestly as "Exchange Activity Value" with a "Social Value Multiplier".
Automatic sign-out after a period of inactivity. Communities can now set an inactivity timeout, so a member is signed out automatically after a chosen idle period — useful on shared, public, or kiosk computers. It stays off unless a community turns it on.
Recent passwords can no longer be reused. When you change or reset your password, the platform now remembers your recent passwords and won't let you set one you've used lately — nudging everyone toward a genuinely fresh, stronger password.
UK address lookup and Ordnance Survey maps. Communities can now opt into Ordnance Survey for two things: precise UK address autocomplete (type a postcode or street and pick a UPRN-backed address) and OS Maps basemap tiles as an alternative to the default map provider. Both are configured per community, the OS key never reaches the browser, and each falls back gracefully when no key is set.
More admin reporting tools. Admins can now export reports as Excel (
.xlsx) files in addition to CSV, download a community-wide audit log as CSV, and find people faster with a new smart member-search panel.A "Report a problem" button on every page. Signed-in members get a floating reporter to send feedback or flag an issue without leaving the page they're on.
Mobile app: more of the platform, natively. The mobile app gained native Polls and Connections workflows and advanced filters on the Exchanges screen, bringing it closer to full parity with the web app.
Fixed
Impact figures no longer count system credits as exchanged hours. Starting balances, admin credit grants, community-fund movements and credit gifts were all being counted as "hours exchanged" and monetised as social impact, across the Impact Report page, the CSV export, and community-health metrics (a new member's starting balance even counted them as an "active trader" and marked them "activated"). Only genuine completed service exchanges count now.
The Impact Report no longer counts pending or cancelled exchanges. One of the page's two data sources included transactions in any state; only completed exchanges count now.
Impact date filters now include the final day. Filtering "to" a date used to silently exclude everything after midnight of that day.
The two customisable footer logos no longer bleed off the edge of the page. The community-set partner logo (left) and "Powered By" image (right) used a fixed size, so a wide logo could overflow its column and spill past the edge of the screen as the window narrowed. Both images now scale down to fit their column at any width.
The community emergency-alert banner no longer logs errors when a check is interrupted. The banner checks for new emergency alerts every 30 seconds; if a check failed or was interrupted (for example while you were still signing in) it logged an error and could briefly clear the banner. A failed check now quietly keeps the alerts already on screen.
The admin "active members" report shows real data again. Members' last-sign-in times stopped being recorded after a back-end migration, so reports of recently-active members came up empty. Sign-in times are recorded again (and backfilled from existing sessions), so activity reports are accurate.
Guardian (parental) consent for young volunteers is now a complete, enforced feature. Members under 18 can no longer apply for a volunteering opportunity, sign up for a shift, or join a shift waitlist until a parent or guardian has approved. When an under-18 member tries, a friendly dialog asks for their guardian's name, email and relationship, and emails the guardian a secure approval link. The guardian lands on a dedicated approval page and confirms with one tap (the page deliberately requires that tap — automated email scanners that pre-open links can't accidentally grant consent). Once approved, the young person can volunteer immediately; admins see the consent (with dates and expiry warnings) on the existing Guardian Consents screen. Adults and members who haven't given a date of birth are completely unaffected. Fully translated across all 11 languages and verified end-to-end in the browser.
Fixed
Approving a new member now actually lets them sign in. On communities where new sign-ups need admin approval, clicking "Approve" sent the welcome email (with its time-credit bonus) but left the account itself locked — the member still saw "pending admin approval" every time they tried to sign in, with no hint anything was wrong. Approval (single or bulk) now fully unlocks the account, and re-approving anyone stuck in that half-approved state repairs them without sending duplicate welcome credits.
The stray "Sign in with a passkey" Windows prompt is gone. Opening the sign-in page started a passkey request in the background that was never cleaned up — it outlived the page, stacked up again on every visit, and fired twice per page load. On some Windows machines this could surface the system passkey dialog out of nowhere, even for accounts with no passkey set up. The sign-in page now makes exactly one silent request per visit (passkey suggestions only ever appear in the email field's autofill dropdown), cancels it the moment you leave the page, and never opens a dialog unless you press the "Sign in with a passkey" button yourself.
Email verification links no longer hang when many sign-ups are pending. Clicking the "verify your email" link could spin for over half a minute and time out whenever a community had a backlog of unverified registrations — the system was checking the link against every outstanding verification token one by one. It now finds the right token instantly, however many sign-ups are waiting. Existing links keep working.
The "resend stuck activation emails" admin tool now works through the whole backlog. Running it repeatedly used to email the same oldest batch of people again and again and never reach anyone beyond the first batch. Each run now skips people who already received their email and moves on to the next group.
The community name no longer vanishes from the web address on the Events page. Opening Events (or filtering it) silently rewrote the address from
/your-community/eventsto just/events— so refreshing the page or sharing that link could land in the wrong community (the platform's default one) instead of yours. The address now keeps your community's name at all times.Screen readers now announce the reaction-filter tabs properly. The tab list in the "who reacted to this post" dialog was reading out a raw internal code instead of "Reaction type filter", in every language.
Downloading a CV attached to a job application works again. Opening an applicant's CV (as the applicant, the job poster, or an admin) always failed with a server error — the download response was built in a way the system rejected at the very last step, so the error pages worked but the actual file never arrived. CVs now download correctly, byte-for-byte.
Small images attached to feed posts no longer show a broken thumbnail. For images already small enough to not need a separate thumbnail, the post still pointed its thumbnail at a file that was never created — so feeds showed a broken image for every small picture. Small images now serve as their own thumbnail.
Daily and monthly community digests now arrive on schedule. The civic digest's "have I already sent this recently?" check was slightly too strict, so daily digest readers were silently skipped every other day, and the monthly digest skipped entire months (every March, plus most months with 30 days). The timing check now has a sensible margin.
A big newsletter send can no longer delay the day's other scheduled jobs. Sending a large newsletter used to occupy the scheduler until the whole send finished, which could silently skip that day's daily digests, reminders, and the midnight leaderboard snapshot. Newsletter sending now works in timed slices, finishing across the next minutes without blocking anything.
Login-streak badges (Week Warrior, Monthly Dedication, etc.) are now actually awarded. The nightly badge job was looking up badge names that don't exist, so it had never handed out a single streak badge; it also only matched people on the exact milestone day, permanently skipping anyone it missed once. Both fixed — anyone whose streak already passed a milestone receives the badge on the next nightly run.
Voice messages work again. Sending a voice message had been failing with an "upload failed" error for everyone since late March — the recording uploaded fine, but saving the message itself was rejected due to a missing database field. The field is now added and voice messages send, play, and notify correctly.
Your match-notification preferences are now actually respected — and mutual-match alerts are back. Saved matching preferences (how often to be notified, opting out) were being read back as the defaults, and the "you have a mutual match" notification had been silently broken since March. Both fixed.
AI assistant feedback and usage metrics work again. Giving a thumbs-up/down on an assistant reply failed behind the scenes (the button appeared to "not stick"), and the admin AI metrics page wouldn't load — the table recording assistant activity was never created in production. It's created automatically on the next release.
Several admin actions that silently failed or corrupted records now work correctly: approving a flagged event no longer makes the event vanish from listings (it was saved with a broken status); approving an AI-proposed care tandem no longer creates an invisible pairing; the blog "bulk publish" button actually publishes; federation neighbourhoods can have communities added/removed and can be deleted; a failed transfer to a partner community is now recorded as "cancelled" rather than a corrupt blank status; and job application status history is recorded again (its viewing pages had also crashed, and the jobs-data erasure request it blocked now completes).
Email polish across all languages. Membership-dues, federation-invitation and new-community welcome emails no longer show raw placeholder text like "{organization}" instead of the real name; names with apostrophes (O'Brien) no longer appear garbled in subject lines and info boxes; password-reset and verification emails no longer greet you with a doubled "Hi Hi John,,"; the "connection declined" email's button now links to the right website; time-credit emails say "hours" in your own language instead of always English; an admin-created account's emailed starting password can no longer be displayed corrupted; and a single malformed bounce report can no longer make the email-status feed double-count bounces.
Repeating volunteer shifts now appear on the right days. Two scheduling faults meant a shift set to repeat "monthly" would instead be created every single day once its start date had passed, and a shift set to repeat "every two weeks" fired on the wrong weeks. Both now follow the schedule that was actually set up.
Monthly repeating events no longer slip off their date. An event set to repeat monthly on the 29th, 30th or 31st used to drift to the 1st of the wrong month after a few repeats (and could skip a month entirely). The series now stays anchored to its day of the month, moving to the last day in shorter months.
The monthly leaderboard season no longer disappears on its final day — and rewards can't be multiplied. On the last day of each month the "current season" looked missing, and every visit to the leaderboard quietly created a duplicate season behind the scenes; at month-end the nightly results job then handed out the season's prize XP once per duplicate. (This genuinely happened in March — top members received the season rewards six times over.) The season now stays visible to the end and duplicates can no longer be created.
Partner-network credit transfers are now protected against double-processing. In the Credit Commons federation protocol (used to exchange credits with partner networks), a transfer confirmation or cancellation delivered twice at the same moment could move the credits twice; a repeated transfer proposal also piled up duplicate pending entries. All of these now detect the repeat and process the credits exactly once. No NEXUS community exchanges credits over this protocol yet, so no real balances were affected — fixed before first use.
Card payments no longer get stuck "retrying" when a confirmation email can't be delivered. If a donor's, buyer's, or premium member's email address had previously bounced, the payment itself went through fine but the system treated the undeliverable confirmation email as a payment-processing failure — causing the payment provider to retry the same notification for up to three days and risking the whole payment-notification channel being suspended. Undeliverable confirmation emails are now simply logged; the payment records were always correct and are untouched.
Profile changes now reach member search reliably. When a member joined, updated their profile, or left, the background task that updates the search index was being placed in a work queue that no worker was ever watching — so those updates silently piled up and search results could show stale or missing members until a full manual re-sync. The queue is now properly watched, and the stuck updates will be processed automatically on the next release.
Account erasure now also removes identity and compliance copies. Following up on this week's erasure work: background-check records (DBS/Garda vetting references and uploaded documents), insurance certificates (policy numbers and certificate files), and identity-verification session results are now deleted when an account is erased — previously they survived because the database's automatic clean-up never triggers (accounts are anonymised rather than deleted). Safeguarding reports remain deliberately retained (legal duty).
GDPR erasure messages to partner timebanks are no longer fire-and-forget. When a member of a federated community deletes their account, the "please erase this person's mirrored profile" message to each partner timebank used to be attempted exactly once — if the partner's server happened to be down, the request was silently lost. It now retries automatically (after 5 minutes, 30 minutes, then 2 hours) and raises a loud operator alert if it still can't get through.
A marketplace order can no longer end up both refunded and paid out. If a refund and a payout release for the same escrowed order happened at the same moment (for example an admin refund racing the automatic release timer), the refund could overwrite the already-completed payout — leaving the buyer refunded and the seller paid for one order. Exactly one of the two outcomes now wins, and the loser is told the order has already moved on.
Submitting the same review twice at once no longer counts it twice. A double-click (or a flaky mobile connection retrying) on the review submit button could create two identical reviews — double-counting the star rating and awarding the reviewer double points. The database now enforces one review per exchange, and the duplicate attempt gets the normal "already reviewed" message.
Double-tapping "Apply" on a volunteering opportunity no longer creates two applications. Two identical applications submitted at the same instant could both go through, showing the volunteer twice in the organiser's list and taking up two spots on a shift. The duplicate is now rejected.
Leaving partner timebanks no longer leave their content behind. When a federation partner is removed, all of the listings, members, events, groups and volunteering entries imported from that partner are now cleaned up, and imported volunteer opportunities are deactivated (kept for history, hidden from browsing). Message history and the credit ledger are deliberately retained.
Single sign-on (SSO) engine — sign in with your organisation's account. Communities can now plug in any standards-based (OpenID Connect) identity provider — Microsoft Entra ID for councils and workplaces, Hivebrite, Google Workspace and others — as configuration, with no code change. Members of the connected organisation see a "Sign in with …" button on the login and registration screens and use their existing work account; no separate password to manage. Administrators get a new Admin → Single Sign-On page to add providers (with a Microsoft Entra ID preset), restrict sign-in to approved email domains (e.g. only
@coventry.gov.ukaddresses), choose whether new accounts are created automatically on first sign-in, and test the connection before enabling it. Security: standards-based flow (Authorization Code + PKCE), identity-provider signatures verified cryptographically, provider secrets stored encrypted and never shown again, and each community's SSO is completely isolated from every other community's. Communities without SSO configured see no change at all. Fully translated across all 11 languages.Permissions-Policy security header. API responses now also restrict which browser features embedded third-party content may use (camera, microphone and location are limited to the platform itself; payment and USB access are denied outright) — closing a gap in the security-header suite alongside the existing CSP, HSTS and frame protections.
Changed
- Feed page polish — social-network-grade feel. A micro-interaction and consistency pass across the whole community feed. The Like heart and Bookmark icons now "pop" with a satisfying spring when tapped (respecting reduced-motion settings); the For You / Recent toggle clearly highlights the active mode; image carousels respond to lighter swipes and their arrows fade in smoothly (and now appear for keyboard users too); hover states across the stories bar, sidebar widgets, link previews, and quoted posts all transition smoothly instead of snapping. If stories fail to load you now get a quiet "Couldn't load stories — Retry" instead of a blank space, and a failed connection request properly rolls back the "Pending" button state. Also fixed two missing screen-reader labels (feed sidebar region and mobile composer) — all new text translated across the 11 languages.
Added
Podcasts module (Alpha) — community audio shows. A new self-contained, tenant-scoped podcasting module. Members can create shows and publish episodes — uploading hosted audio (with a live upload progress bar and clear, specific errors if a file is the wrong type, too large, or fails to save) or linking an external audio URL — with cover art, categories, visibility (public / members-only / private), transcripts, and chapters. Listeners get a built-in player with 15s-back / 30s-forward skip, variable speed, a draggable keyboard- and screen-reader-accessible progress bar, chapter jump-links, and a clear message when an episode's audio can't be loaded (instead of a silently broken player). Members can follow shows, react to episodes (the button now correctly reflects whether you've already reacted), download transcripts, and report episodes to moderators. A Podcast Studio lets members manage their own shows and episodes with a directory-readiness checklist, per-episode media status, and on-brand confirmation dialogs. Public shows expose an Apple/Spotify-compatible RSS feed. Tenant admins get a moderation queue (approve / reject / flag shows and episodes), a member-report queue (resolve / dismiss / escalate, with reasons shown in plain language), RSS feed validation, and listen analytics (completion rate, unique listeners, retention, client breakdown, top episodes). Privacy-preserving listen analytics, optional media scanning/processing hooks, and local-or-cloud media storage are configurable per tenant. The module ships marked "Alpha" and off by default — each community opts in. A single report can never hide a creator's episode on its own (it takes several independent reports, or a community switching moderation on, to auto-flag), so reporting can't be weaponised against a creator. Fully translated across all 11 languages.
Mobile app now supports Light and Dark mode. The Timebank Global mobile app was previously locked to a dark theme. It now has a proper appearance setting — System (follow your phone), Light, or Dark — chosen in Settings and remembered between sessions. The whole app (backgrounds, cards, text, status bar, and navigation) switches instantly and stays consistent, with each community's brand colour preserved in every mode. Available in all 7 mobile languages.
Mobile app feedback now feels native and on-brand. Across the entire mobile app, the old operating-system pop-up alerts — for "saved", "couldn't connect", form-validation messages, and "are you sure?" confirmations — have been replaced with the app's own branded toast notifications and confirmation dialogs (consistent styling, haptic feedback, and the community's colours) instead of generic grey system boxes. This spans every screen: wallet, messages, marketplace, groups, events, volunteering, jobs, settings, profile, federation, and more (≈359 prompts across 62 screens), with no change to the wording you see.
Courses module (Alpha) — community learning. A new self-contained, tenant-scoped learning module: courses organised into sections and lessons (video, rich text, PDF, and external embeds), free and members-only enrollment, per-lesson progress tracking with automatic course completion, and auto-graded multiple-choice quizzes. Any member can author courses through a course builder by default (a tenant can restrict authoring to instructors/admins); admins get a moderation queue, instructor-grant management, categories, and tenant analytics. The module ships marked "Alpha" in module configuration and is off by default — each community opts in per tenant. An "Alpha" badge is shown on the member-facing Courses pages. Learners can discuss each lesson in threaded comments and leave star ratings + written reviews. Lessons support drip scheduling (release a set number of days after enrolment or on a fixed date), enforced server-side and shown as locked-with-unlock-date in the player. Instructors get a per-course analytics page (enrollment funnel, completion rate, average quiz score, and a per-lesson completion chart) and a grading queue for quiz attempts with short-answer/essay questions (set score, pass/fail, and feedback). Course completion awards gamification XP and a graduate badge and issues a printable completion certificate (download from My Learning). Learners get enrolment and completion notifications (in-app + completion email with a certificate link), each rendered in the recipient's preferred language. All courses are free to enrol in. Courses can be linked to community groups, and a group page surfaces its "Recommended courses". Courses support prerequisites (enrolment is blocked until the required courses are completed, shown on the course page) and cohorts (cohort-paced course groupings). Course content is indexed into the AI semantic-search embedding store (via a model observer), so the assistant and recommendations can surface relevant courses. Learning paths, feed celebration posts, and full keyword (Meilisearch) search-results integration are scaffolded for later phases. Fully translated across all 11 languages.
AI assistant ships fully trained out of the box. Every new tenant is now auto-seeded with 38 comprehensive AI module docs covering the platform overview, timebanking philosophy, every module and feature (listings, wallet, messages, feed, dashboard, profile, notifications, settings, events, groups, volunteering, jobs, marketplace, blog, resources, polls, ideation, organisations, group exchanges, federation, gamification, goals, connections, reviews, AI chat, search, caring community, newsletter), account security, GDPR/privacy, accessibility, mobile/PWA, troubleshooting, and admin workflows. Each doc has 6–21 trigger keywords (including natural-language phrases like "how does it work" and "new here") and a body sized to fit the prompt-injection limit. All 12 existing tenants were backfilled (456 docs inserted). Tenant admins can still edit, disable, or add their own custom docs on top — the seed is idempotent and never overwrites customisations. Relevance ranking improved to score by keyword-hit count × match length and inject the top 4 most relevant docs per turn.
Community Fund administration. New admin module under Timebanking for administering a shared community time-credit fund, with its own service, API endpoints, sidebar/breadcrumb navigation, a schema fix migration, and full translations in all 11 languages.
Configurable "Powered By" footer branding and partner logo. Tenants can now show a "Powered By" slot in the footer (label, light/dark logo images, and a click-through URL) and a separate partner-logo slot with its own link, all configurable by the platform owner with upload endpoints. NEXUS branding ships as the default. The footer attribution panel was redesigned to accommodate this.
Mobile app migrated to HeroUI Native v3 + NativeWind. The Expo/retired web-wrapper mobile app was rebuilt on HeroUI Native v3 with NativeWind across its UI primitives, auth screens, tab screens, and modal screens, with deep-link, image, offline-detection, and "More" menu fixes and updated EAS build configuration.
Safeguarding staff are alerted the moment a report escalates or is assigned. In the Caring Community safeguarding workflow, a report that breached its review deadline used to escalate silently — no one was told, and staff only noticed by checking the dashboard. Now the assigned reviewer and everyone with safeguarding view permission get a bell, device push, and email (in their own language) when a report escalates (covering both the automatic SLA breach and a manual escalation), and the assigned reviewer is notified the instant a report is assigned to them. These alerts are staff-only and contain just the report's reference, severity, category and deadline — never the case details — and they never reach the person the report is about.
Moderators are alerted when content is reported or auto-flagged. Reports on feed posts, social content, listings and marketplace items — and job postings automatically flagged as possible spam — used to be written to a queue with no alert, so moderators only found them by manually checking. Admins, brokers and coordinators now receive a bell, device push and email (each in their own language) the moment any of these arrive, so nothing sits unseen. The person who reported is never identified in the alert.
Sellers and content owners are now told when moderation acts on them (transparency). Previously, if a marketplace listing was removed, a seller account was suspended, or a post/comment was taken down, the affected person often learned nothing — no reason, no way to contest. Now sellers are notified at each step of a marketplace report (under review → outcome → appeal result) and whenever an admin rejects a listing or suspends an account, and post/comment authors receive a clear email explaining that their content was removed and how to contest it. Every notice is in the recipient's own language, states what happened and how to appeal or contact support, and never reveals who reported them.
Admins can now see device-push delivery health. A new "Device push" panel on the admin Email Deliverability page shows, for the selected time window, how many push notifications were delivered, partially delivered, or failed across web and mobile (FCM), the overall success rate, and the most recent failures with their reason — so a community can confirm push is actually reaching members' phones and browsers, not just being attempted. Backed by a new push delivery log; push send failures now also surface in error monitoring instead of being silently dropped.
Job moderation decisions now arrive by email too. When an admin approves or rejects a job posting, the poster now also receives a durable email in their own language — for rejections, including the reason and how to edit and resubmit — in addition to the existing in-app bell and push notifications (which previously left no lasting record). Listing approvals now also send a device push for channel parity, and use the tenant-safe notification writer.
Volunteer opportunities now have a "Share with partner communities" choice. Until now, every active volunteer opportunity was automatically shared with federated partner communities, with no way to keep one local. Organisers now choose per opportunity — a switch on the create form and on the opportunity page (owner only). Existing shared opportunities stay shared; new ones start private. This also fixed a subtle bug where opportunities imported from a partner could be re-broadcast back out to the network.
Five admin tools that existed "under the hood" now have actual screens. The platform had working back-ends with no way to use them; admins now get: Help FAQ editing (write, reorder, publish/unpublish and delete the questions shown in the Help Centre), Search analytics (what members search for, trending queries, and searches that return nothing — a goldmine for spotting missing content), Donation refunds (see all donations and issue a Stripe refund with a clear are-you-sure step stating the amount), Group tags & collections (organise groups with tags, curated collections, and auto-assign rules), and Residency verification (review members' residency declarations and approve, or reject with a reason). All five are fully translated in the 11 languages.
You can now see and delete the reviews you've written. The Reviews page has a new "Given" tab listing every review you've left for others — who it's about, your rating and comment — with the option to delete one (after a confirmation). Previously there was no way to see your own written reviews at all. Along the way, two glitches on the existing tab were fixed: the "load more" control on received reviews didn't work, and a failed delete showed nothing instead of an error message.
Welcome credits now default to 5 everywhere. Communities that never configured a welcome balance behaved inconsistently: members approved by an admin received 5 credits, while members who joined a self-serve community received nothing. Both now default to 5 credits, matching the long-standing approval behaviour. Communities that don't want welcome credits can set the amount to 0.
Security: all known vulnerable components updated. The daily security scan had been flagging outdated third-party components: the server container's operating-system packages (including the web server, where patched versions existed for a remote-code-execution and a denial-of-service issue) and one critical package in the mobile app. The container now installs all security patches every time it's built, the mobile package is updated, and the scan's reporting was fixed so findings always reach the GitHub Security tab.
Welcome credits are now actually granted to new members. Communities could configure a starting time-credit balance for newcomers, but on self-serve communities it was never paid out — the setting did nothing. New members now receive it the moment their account becomes active (at email verification, or immediately for admin-created accounts; communities with admin approval already granted it at approval). Strong safeguards ensure nobody can ever receive it twice, even across the different signup routes, and the setting is honoured whichever admin page it was configured on.
Accepting a municipal copilot proposal now actually publishes it. Previously, accepting an AI-polished communication proposal only recorded the decision — nothing went out, and the admin had to re-create the text by hand in another screen. Accepting now broadcasts the polished text as a community announcement (banner + push notification) in the same step, records which announcement it became, and offers a Publish retry button if the broadcast fails. Re-accepting can never publish twice.
The platform now notices within minutes if background processing stops. The June outage went undetected for 5 days because every health indicator only checked that the queue manager was running — not that work was actually being done. Two new safeguards close that gap: a tiny "heartbeat" task is sent through the real queue every 5 minutes and an independent watchdog raises an alarm (error log + monitoring alert, at most one alert per 6 hours) if heartbeats stop coming back; and the container health status itself now requires a live worker process, not just the manager.
Fixed
Guardian (parental) consent for young volunteers now actually works. The consent system for under-18 volunteers was broken at every step without anyone noticing: when a parent clicked "give consent" in the email, the approval silently failed; withdrawing consent silently failed; the nightly job that expires year-old consents crashed every night (confirmed in production logs); and the admin "Guardian Consents" page always showed an empty list even when records existed. All four are fixed and covered by new tests that use the real database (the old tests used stand-ins, which is exactly how the wrong database column names slipped through). The admin page also no longer exposes the secret consent link — previously a community admin could have approved consent on a parent's behalf.
Stopped a runaway email loop wasting resources every half hour. When a reminder email can never be delivered (the address has hard-bounced or marked us as spam), the volunteer shift-reminder system kept retrying the same dead addresses every 30 minutes, forever — in production it was attempting 36 impossible sends twice an hour, around the clock. Such addresses are now marked as handled once and never retried, while genuinely temporary email hiccups still retry as before.
Quieter, healthier nightly maintenance. The nightly clean-up job logged a warning every single night while trying to tidy a database table that has never existed; it now checks first and skips silently.
"Delete my account" now erases much more of your personal data. A deep audit found that account erasure — while already covering profile, messages, volunteering, connections and more — left several things behind: job application CVs and cover letters (including the CV files themselves), your stories, marketplace seller business details (address, VAT number, payment account link), delivery addresses and notes on marketplace orders, poll votes, personal goals and their check-in notes, course learning history, comments on feed posts, and voice-message recordings on disk. All of these are now deleted or scrubbed when an account is erased, and a permanent automatic test guards the full list so future features can't quietly fall out of it. Three categories are deliberately kept and were confirmed correct: safeguarding reports and vetting records (legal retention duties) and time-credit transaction amounts (the community ledger, with your name already anonymised).
Event reminder emails now show the event time in your community's timezone. Event times are stored internally in universal time (UTC); the website and app already convert them back to your local clock, but the reminder emails printed the raw UTC time — so an Irish community's 7pm summer event read "6pm" in the email. Reminder emails now use the community's configured timezone setting.
Event reminders no longer endlessly retry dead email addresses. The same retry-storm fix applied to volunteer shift reminders also applies to event reminders (it was attempting dozens of impossible sends per day in production) — and members with an undeliverable email address still get their in-app bell reminder.
Mobile app: voice messages now play back. Recorded voice messages sent but showed a red "Failed" badge when you pressed play — the app was handing the audio player a server-relative path (e.g.
/uploads/…) instead of a full web address, which the player can't load. Voice (and any other) media is now resolved to an absolute URL the same way images already were, so playback works — including for voice messages that were already sent.Mobile app: comment windows and every other slide-up panel now actually open. A deep timing flaw meant the component library could silently ignore the 'open' command in production builds — comment windows, apply forms and pickers fetched their content but never appeared on screen (in development builds, which run slower, it always worked — which is why it survived testing). The open command is now issued with a proper delay and re-asserted automatically, verified end-to-end on a real device build: the comment sheet opens first tap, comments post and appear.
Mobile app: see who reacted, Facebook-style. Feed cards now show the familiar summary line — overlapping reaction emojis plus 'Anna and 3 others' — and tapping it opens a panel listing everyone who reacted, filterable by emoji, with each name linking to their profile.
Mobile app: comments catch up with the web. You can now reply to comments, edit or delete your own (press and hold for the menu), and like comments — none of which the app offered before. Timestamps throughout the feed now appear in your chosen language, reaction and counter labels are properly translated in all 7 app languages, and failed likes/saves now tell you instead of silently undoing themselves.
Mobile app: smoother feed scrolling. Feed cards no longer all re-render when one changes, loading the next page shows placeholder cards instead of a spinner, images recycle correctly during fast scrolling, and the next page starts loading earlier so you rarely hit the bottom.
Mobile app: the Like button now responds to every single tap. A component-library quirk meant that adding long-press support to the Like button silently broke ordinary taps — a quick tap did nothing at all, while a long hold could register a stray like. The press handling was rebuilt (verified live on a device against the running API): one tap likes instantly, holding the button slides out the emoji picker while your finger is still down — just like Instagram — and releasing after the picker opens never fires an accidental like.
Mobile app: emoji reactions arrive, and the Like button finally behaves. The feed's Like button now matches the web app: a quick tap likes (and stays highlighted — previously the highlight vanished the instant the server replied, because the app read a field the server never sent), and a long-press opens the full emoji picker (👍 ❤️ 😂 😮 😢 🎉 👏 ⏰) — the same eight reactions as the web, on every reactable feed card.
Mobile app: slide-up panels open on the first tap. Comment sheets and other slide-up panels sometimes needed two or three taps to open — a quirk in the sheet library could fire a phantom "close" signal the moment a sheet was created, instantly cancelling it. Phantom closes are now filtered out everywhere, so every sheet opens first time.
Account deletion now properly erases volunteering data. Deleting an account anonymised the member's profile but left their volunteering records untouched — vetting/credential records, wellbeing check-ins, accessibility needs (including emergency contacts), guardian consent details, training records, certificates and donor names all survived a GDPR erasure request. Deletion now removes those sensitive records outright and scrubs personal text and donor details from the records that must remain for organisation accounting (hours and donation amounts are kept, with no name attached).
Volunteering: expense decision notifications now land on the right page. The "your expense was approved/rejected" email linked to a page that doesn't exist, silently dropping members on the volunteering home tab. It now opens the Expenses tab directly.
Volunteering: the shift waitlist now actually works. Joining a waitlist looked fine, but the machinery behind it was never connected — when a spot opened up, nobody was told, and the "next in line" could never be moved onto the shift. Now, when someone cancels a shift signup (or an organisation declines a previously approved volunteer), the first person on the waitlist instantly gets a notification in their own language, sees a highlighted "Spot available — claim it" card on their Waitlist tab, and one tap signs them up (with a safety re-check so a claim can never overfill the shift). Unclaimed offers automatically pass to the next person after 48 hours, and leaving the queue while holding an offer hands it on immediately. Translated across all 11 languages.
Volunteering: shift swaps can no longer be completed after the shift has already happened. A swap accepted (or admin-approved) late used to go through even if the shift had started, corrupting attendance records. It's now politely refused.
Volunteering: admin CSV exports are now safe to open in Excel. Volunteer names or messages crafted to look like spreadsheet formulas can no longer execute when an admin opens an exported approvals or hours file.
Volunteering: the log-hours form now stops impossible entries before submitting. The hours field accepts 0.25–24 in the form itself instead of letting the server reject it afterwards.
Volunteering: cash and bank-transfer donations no longer vanish into a permanent "pending" state. Donating through the Donations tab without paying by card recorded the donation, but nothing could ever confirm it — it never counted toward the giving-day campaign total or donor count, and admins had no way to mark the money as received. Admins now get a "Mark completed" button on the Donation Refunds page for offline donations (cash, bank transfer, PayPal); confirming one adds it to the campaign total exactly once, even if clicked twice. Card payments are unaffected — they still confirm automatically. Translated across all 11 languages.
Volunteering: a removed group-shift member could never be re-added. Removing someone from a group shift reservation and then adding them back always failed with a generic server error. Re-adding now works. The same fix closed two quieter problems: group member records were being saved under the wrong community, and two leaders adding members at the same moment could overfill the reserved slots — capacity is now checked atomically.
Volunteering: shift waitlist positions could develop gaps when two people left at once. Queue reordering now locks each entry before renumbering, so positions stay contiguous.
Volunteering: opportunity pages with many shifts loaded slower than needed. Spots-remaining counts for all shifts are now fetched in one query instead of one query per shift.
Mobile app: typing anywhere now uses proper full-height composers. Starting a group discussion, requesting an exchange from a listing, reporting a listing, adding a skill, and creating a goal all squeezed your typing into tiny inline boxes that the keyboard covered. Each of these now opens a proper slide-up panel with room to write, and the keyboard never hides what you're typing.
Volunteering: admins no longer see owner controls on everyone else's opportunities. A flag that was meant to mean "this is your post" actually meant "you have admin powers", so admins saw Edit buttons and an approve/decline applications panel on other people's volunteer opportunities — and their own "Apply" button silently refused to work on every post. Applying now responds instantly with clear feedback, applying to your own opportunity is politely refused with a proper message (in all 11 languages), and owner controls only appear on posts you created. Organisation admins still manage everything from the organisation dashboard.
Organisations list no longer shows 0 members / 0 listings / 0 opportunities for everything. The server never sent the counts; it now calculates real opportunity, volunteer, hours and rating figures for each organisation — on web and mobile alike.
Mobile app: "Read in app" on the Support & legal page looked dead. Tapping it actually worked, but the document appeared at the very top of the page — off-screen if you'd scrolled down. Documents now open in a slide-up reader you can't miss.
Mobile app: profile tidy-up. The achievements section no longer arrives pre-expanded, and the two confusing full-width cards beneath it ("appreciations" and "collections" — both real features that also exist on the web) are now one compact, clearly-labelled pair of rows.
Mobile app: live updates were silently broken — now fixed. The mobile app asked the server for its real-time connection details at an address that didn't exist, and the failure was swallowed silently — so new-message badges, live chat updates and other instant notifications never worked in the mobile app. The app now uses the correct address, and the server also answers at the old one, so phones with the current version installed start receiving live updates again as soon as the server is updated — no app-store update needed.
Mobile app: your language choice now sticks. Picking a language in the mobile app's settings worked until you closed the app — on the next launch it silently reverted to your phone's language. The choice is now remembered. Dates and times throughout the app (56 places across 36 screens) also now follow the language you chose instead of the phone's region setting.
Mobile app: buttons no longer turn invisible on light community colours. For communities with a light brand colour, selected filter chips, tag pickers, the floating "+" button icon and button loading spinners painted white-on-light — unreadable. Text and icons on brand-coloured buttons now automatically switch between black and white for proper contrast.
Changing your password in-app now requires the same strength as everywhere else. Registration and the email reset flow required 12 characters, but the in-app "change password" screen (and its server check) only required 8 — a weaker back door. All three flows now require 12, and the mobile reset screen no longer accepts a password the server would reject anyway.
Mobile app: small polish and safety fixes. Five screens that could show a blank page if something went wrong (edit profile, change password, image viewer, new message, quick create) now show a proper "something went wrong" recovery screen; a handful of unlabeled icon-only buttons (delete, image thumbnails, star ratings) are now announced correctly by screen readers; and the crash reporter now strips login credentials from anything it sends.
Deleting a recurring event series now tells the people who'd signed up. Cancelling a series already notified attendees, but deleting it removed every future occurrence silently — people could show up to an event that no longer existed. Deletion now sends the same cancellation notice (bell, email and push, each in the recipient's own language) to everyone going, interested, invited or waitlisted on a future occurrence — exactly once per person, and never for occurrences that were already cancelled (those attendees were told at the time).
Roughly 1,850 missing translations filled in across all 10 non-English languages. A series of recent features (support reports, volunteer alerts, guardian notifications, partner management, data retention, password history, UK address lookup, CRM admin, comment-reply emails, and more) had shipped with English-only text, so members using Irish, German, French, Italian, Portuguese, Spanish, Dutch, Polish, Japanese or Arabic saw English in those places. All are now properly translated (not machine-copied English). A new automated check blocks any future code change that adds English text without the other 10 languages — closing the gap that let these sit unnoticed for weeks.
A review you delete can no longer be brought back by an admin. Previously, a review deleted by its author looked identical to one a moderator had rejected, so an admin working the moderation queue could accidentally restore it. Author-deleted reviews are now marked distinctly: they no longer appear in any moderation queue, and the restore/hide actions refuse them outright.
Background job processing restored (queued work had been silently stuck since 6 June). A version mismatch between two framework components meant every background worker crashed the instant it started — while the system still reported itself "healthy". Anything handled by the background queue (federation syncing between communities and some queued notifications) quietly piled up instead of being processed; nothing was lost, and the backlog is worked off automatically once this fix is deployed. The mismatched component has been updated to the release that fixes the incompatibility.
Joining a group twice by double-click is now impossible at the database level. A fast double-click on "Join" could create two membership records (double member counts, duplicate welcome messages). The database now enforces one membership per person per group — existing duplicates are cleaned up automatically — and the same hard guarantee was added for marketplace escrow records. The join action treats a lost race as "already a member" instead of an error.
Marketplace payouts can no longer be released twice. A buyer clicking "confirm received" at the same moment the automatic timed release ran (or a double-click) could complete the same order twice — doubling the seller's recorded sales and revenue and sending duplicate payout notifications. Completion and escrow release are now claimed atomically; exactly one path wins.
Volunteer-organisation wallets can no longer deadlock. A member depositing into an organisation at the same moment the organisation paid a volunteer could lock both records in opposite orders, failing one action with a server error. Both paths now lock in the same order.
Transaction XP can't be double-awarded on a queue retry, and search results show listing category names again (a leftover legacy column was shadowing the real category link, so the name was always blank).
Group exchange completion can no longer pay people twice — or short-change them. Two clicks of "Complete" in quick succession could run the whole credit distribution twice; completion is now claimed atomically so the second click is harmless. Separately, each participant's share was being rounded down to whole hours (three providers splitting 10 hours got 3+3+3 credited while the receiver paid 10 — a credit vanished; shares under one hour paid nothing at all): exact 2-decimal shares now flow through. Receivers also can no longer be driven into negative balance — completion fails cleanly if a receiver lacks the credits, matching every other payment path.
Admin panel: around 70 more actions no longer claim success when the server refused. The same false-success flaw fixed in the member app last week was swept from the admin panel: safeguarding escalations, tenant provisioning approvals, push-campaign dispatch, paid analytics subscriptions, identity-provider credentials, compliance registers, group management, menu building, translation settings, and ~30 more screens now report failures honestly and stop discarding what you typed when a save fails.
Duplicate-notification protection extended to nine more background senders. Job-alert fanouts, connection requests/acceptances, group joins, onboarding emails, safeguarding staff alerts, and two admin background jobs lacked the platform's standard guard against a queue retry re-sending every email; all now send at most once.
Public knowledge-base article links no longer error. Opening an article by its web address failed every time due to a misnamed counter column (the in-app route was unaffected).
Goal reminders respect privacy. A member could attach a reminder to someone else's private goal and the reminder email would reveal that goal's title; reminders are now limited to your own goals (or public ones).
Deleted accounts no longer appear in member search, transfer recipients, or @mentions. With account deletion now working, anonymised "Deleted User" entries could surface in pickers — and credits sent to one would be unrecoverable. All people-pickers now exclude them.
Help FAQ category filter no longer errors (wrong column name on a public endpoint).
Recurring events: "only this event" edits now stick. Editing a single occurrence didn't detach it from the series, so a later "all future events" edit would silently overwrite it. Also, deleting a series is now all-or-nothing (a mid-way failure can't leave it half-deleted).
Deleting your account works now — and properly anonymises your data. Closing an account failed with an error because it tried to set an account state the database doesn't allow, which also meant the personal-data anonymisation step never ran. Account deletion now completes: the account is deactivated, sign-in is blocked, and name/email/phone/location are anonymised with deletion timestamps recorded.
Deleting your own review works now. It failed for the same kind of reason (an invalid state value); deleted reviews are now properly hidden everywhere.
Marketplace bulk "deactivate" works now. It also wrote an invalid state; deactivated listings now return to draft (and can be re-activated).
Super-admin "move user to another community" no longer reports failure after succeeding. The screen always showed an error even though the member had actually been moved — a confusing half-state. It now reports honestly (and the log no longer claims the member's content moved with them, which it never did).
Onboarding settings changes take effect immediately. Saving onboarding configuration tried to clear the cache through a service name that doesn't exist, failed silently, and changes only appeared when the cache happened to expire.
Cookie-consent changes are recorded in the audit trail again. Since April these were written to a table that had been removed (replaced by its correctly-named twin), so the GDPR consent history was silently lost.
Also hardened in this pass: the general file-upload endpoint stored files under a folder named after the user's account number and ignored the requested category (now fixed); and the optional Vault secrets integration, whose client class didn't survive the Laravel migration, now fails softly to normal configuration instead of crashing if ever enabled.
Group challenge rewards actually arrive now. Completing a group challenge wrote the XP reward into each member's activity history but never added it to their actual XP balance — so it couldn't be spent in the XP shop and didn't count on the leaderboard. Challenge rewards now go through the standard XP pipeline (balance, leaderboard, level-up check, live update), the same as every other XP award.
Donation receipts now appear in the recipient's language. The wallet-history line for a credit donation was permanently stored in whatever language the donor was using. It's now stored in the recipient's language, since it's their wallet history that shows it.
Deleting or cancelling a recurring event now covers the whole series. Previously, deleting a recurring event removed only the first one — up to 52 future occurrences stayed live with no way to remove them except one at a time — and cancelling it cancelled only the first occurrence, so people signed up for later dates were never told. Deleting now also removes all future occurrences (past ones are kept for attendance history), and cancelling now cancels every future occurrence and notifies everyone affected, with a clear warning in both dialogs that the whole series is included.
Editing a recurring event now asks "only this event, or the whole series?" Edits to a series previously applied silently to just the one occurrence. Organisers now get a choice; series-wide edits update details like title, description, and location on every future occurrence (date/time changes deliberately stay per-event so the schedule can't be collapsed onto a single timestamp — a server-side guard now enforces this too).
Around 200 messages that always appeared in English are now fully translated. Across volunteering (shifts, swaps, waitlists, group reservations), group conversations and chatrooms, private messages, sub-accounts, skills, the XP shop, and polls, the platform's responses — errors like "You are not signed up for the source shift" or "Not enough XP" — were written directly into the code in English and shown to every member regardless of their language. All of them now go through the translation system, with real translations (not English placeholders) in all 11 languages. Separately, ~190 "Loading" labels in the admin panel that screen readers announced in English are now translated too.
Polish round from the June platform audit. Admin confirmation dialogs for deleting groups, group types, AI assistant docs, and bulk actions now use the platform's styled dialog instead of the browser's plain pop-up; "Total" count chips in the Polls, Ideation, and Algorithm admin pages show the actual number again; the volunteering opportunity search no longer fires a request on every keystroke; Caring Community transfer notifications can no longer be sent before the transfer is final; and a developer verification script broken since the Laravel migration was repaired, along with stale references in the README and contributor docs (React 19, removed animation library, decommissioned legacy admin) and missing example environment keys.
Match emails greet you by name again — and no longer invent a match score. The hot-match, mutual-match, and digest emails always fell back to a generic greeting because the recipient's identity was never passed to the email builder. Mutual-match emails also asserted a fabricated "75% match" when no score had been computed — the score badge now only appears when a real score exists.
Match and digest emails are now fully translated. Several English fragments leaked into every language: the "View" link and frequency word in the activity digest, "(Xkm away)" in hot-match alerts, the Offer/Request badge, day/week/fortnight period words, and fallback phrases like "a skill you need". All now render in the recipient's language (new translations added for all 11 languages), digest timestamps use localised month names, and the digest email finally carries the community's name and a proper footer like every other email.
Accessible (GOV.UK-style) frontend hardening. Pages for disabled modules are no longer reachable by direct URL (feed, messages, member directory, and exchanges now respect each community's module settings, matching the main app); the registration page's terms/privacy links no longer point at a dead address on the accessible domain; posting to the feed is rate-limited like every other form; the page no longer pretends JavaScript is available before it loads (restoring the progressive-enhancement guarantee); the contact form's fallback subject is translated; and the feed's type-filter hint is now announced by screen readers.
The app no longer claims success when the server said no. A platform-wide flaw meant that when the server rejected an action (for example confirming exchange hours, hiding a feed post, saving a marketplace coupon, deleting a goal, dismissing a match, or saving an item), the app often showed a green "success" message and updated the screen anyway — the change silently never happened and reappeared on reload. Around 40 such spots across exchanges, the feed, marketplace, goals, messages, notifications, wallet transfers, and volunteer applications now check the server's actual answer: real failures show a clear error message, optimistic changes are rolled back, and wizards no longer advance with nothing saved. Affected flows included all six exchange actions (accept/decline/start/complete/confirm-hours/cancel — credit-affecting), merchant onboarding steps, and coupon saving.
Failed page loads no longer masquerade as empty pages. When loading search results, connections, listings, wallet transactions, exchanges, or the notifications flyout failed, the page showed a friendly "nothing here yet" empty state with no hint anything went wrong — and the exchanges page could get stuck on a permanent loading skeleton. These now show a proper error message with a retry option.
Feed: a deleted poll no longer hammers the server. A poll card whose data could not be fetched (e.g. the poll was deleted) refetched in an infinite loop; it now shows the poll error state once.
Messaging someone for the first time works from "Message" buttons. For members with no existing conversations, clicking "Message" on a listing, profile card, or seller page landed on an empty inbox and did nothing. It now opens the new-conversation screen as intended.
Conversations: messages arrive without a refresh on communities without live updates. The fallback that checks for new messages every few seconds never started when opening an existing conversation, so incoming messages only appeared after switching tabs or refreshing. Also fixed: attaching a second file no longer breaks the first attachment's preview, edit/delete failures now show an error instead of nothing, and reaction counts no longer drift on unexpected server replies.
Wallet: the "Load more" button works after using the filter tabs. Touching any transaction filter permanently disabled further pagination.
Events: changing filters while older events were still loading could mix results from the old filter into the new list. Stale responses are now discarded; failures while loading more events show an error. Also, RSVPing no longer needlessly refetches the event-series list, and two redirects (profile login, conversation not-found) now keep you on your community's address.
Matches: volunteering suggestions no longer link to a "page not found".
Recurring events actually recur now. Ticking "recurring" when creating an event silently created a single, one-off event — the recurrence settings (weekly/monthly, days, end date/count) were sent to an endpoint that ignores them. Event creation now uses the recurrence-aware endpoint, which creates the series template plus all its occurrences.
The platform-owner Billing Control panel works again. Every action on the super-admin billing dashboard (loading the tenant snapshot, assigning plans, pause/resume, grace periods, CSV export) called a malformed API address and failed. All actions now work, failures show a real error message instead of being silently ignored, and the CSV export goes through the authenticated download path.
The activity-digest email frequency setting saves correctly now. Choosing how often you receive the digest email in Settings → Notifications always showed a save error (and never loaded your current choice) because the setting was sent to an address the server didn't recognise. It now loads and saves properly.
Two admin CSV exports repaired. "Hours by category" and "Inactive members" report exports always failed with a validation error due to a report-type name mismatch between the export button and the server.
The public municipality events calendar page loads now. The page existed and the server logic existed, but the API route connecting them was never registered, so the page always showed an error.
Requesting a plan upgrade from the admin Billing page actually sends the request now. The "request upgrade" button posted without authentication, was always rejected, and still showed "Upgrade sent". It now sends authenticated, reports failure honestly (falling back to a pre-filled email), and the email subject is translated.
XP Shop purchases work again. Buying an item with XP always failed with "Purchase failed" (and no XP was deducted) because the purchase record was written with the wrong column names for the purchases table. Purchases now record correctly.
Scheduled group posts now actually publish. Two problems meant a group post scheduled for later never appeared: the background job that publishes due posts was never added to the platform's schedule, and discussion-type posts were written with a column the posts table doesn't have — which also quietly created an empty duplicate discussion shell on every attempt. Scheduled group posts (announcements and discussions, including recurring ones) now publish every five minutes as intended.
Match suggestions learn from your activity again. Saving, dismissing, viewing, or contacting a match suggestion is meant to teach the matching engine your preferences, but every one of those signals was silently discarded due to a column mismatch in the match-history table. The engine now records them, so "Top matches" personalisation improves with use.
Group recommendation feedback is recorded again. Clicks, joins, and dismissals on "Recommended groups" were silently dropped (the write was missing its community identifier), so the recommendations admin analytics always showed zero activity.
Auto-translation now honours the community's default language. When translating member-written content, the platform looked up the community's default language in the wrong place and always fell back to auto-detection. It now reads the community's configured default correctly.
Newly provisioned communities get their federation defaults. Communities created through the self-service application/approval flow were silently skipping all federation setup (wrong column names on all three federation tables), leaving them invisible to the federation network. They now get the same default-on federation features as communities created by an admin, and are auto-whitelisted by the approving reviewer.
Federation activity logging and group achievements repaired. Incoming federation events from partner networks failed to write their audit-log entries, and the group achievements engine ("First Steps", "Community Builders", …) could never award or list achievements — both due to column mismatches. Both now write correctly.
Cancelling an event works again. A code-placement slip in the recent security hardening pass put an attendee-roster privacy check inside the event-cancellation routine, where it crashed every cancel attempt (organisers and admins saw a generic error and the event stayed live, with no notifications sent). Cancellation now works as before, and the roster privacy check sits where it was meant to — limiting who can browse an event's attendee list to its organiser, admins, and fellow attendees.
The accessible frontend's community-chooser page now shows the right feedback link. The "feedback" link in the footer of the accessible (GOV.UK-style) community chooser pointed at one specific community's contact form instead of the general platform feedback email address. Tenant pages keep their own contact form link.
Leftover English text translated in the Italian, German, and French interfaces. An i18n sweep across all 11 languages found 14 strings still showing in English: the Italian Jobs module (loading message, close/reopen-vacancy confirmation dialogs, hiring-pipeline screen-reader labels, and the bias-audit job search) and Italian blog (loading messages and the category filter), plus the course grading form's "Feedback" label in German and "Score" label in French. All now display properly translated.
The Members directory (and other pages) no longer break with "Something went wrong" after the app has been used across several updates. Over time, as new versions of the app shipped, the browser quietly kept a copy of every past version's translation files — these piled up until the browser's local storage filled completely, at which point even saving a tiny setting (like whether you prefer the Members list in grid or list view) would fail and take the whole page down with it. The app now clears out old, leftover translation copies on startup so storage stays tidy, treats a storage failure as harmless instead of crashing the page, and its built-in storage clean-up now correctly targets those translation caches when space runs low. As a safeguard, every place in the app that saves a setting to local storage now goes through this same self-healing path, so a full browser store can no longer crash any page — it quietly frees up space and carries on.
New communities again launch with default categories, member attributes, and navigation menus — however they're created. A regression introduced during the Laravel migration meant newly created tenants were no longer seeded with their default member attributes (offer/request filters such as "Tools Provided", "Wheelchair Accessible", "References Available") or their default navigation menus (the main header menu and footer menu). New communities now start with the full default set again — eight categories, nine member attributes, and both navigation menus — so admins and members aren't faced with an empty filter list or blank navigation on day one. The defaults now come from a single shared seeder used by both the admin "create community" path and the self-service approval/provisioning path, which previously seeded neither (so communities created through approval still launched empty). The seeded navigation's auth/feature visibility rules are now also honoured by the server-side menu renderer (mobile and search-crawler views), not just the React app, so members-only and feature-gated links no longer show to signed-out visitors on those views. (The one Ireland-specific legacy attribute, "Garda Vetted", is seeded as the globally-neutral "Background Checked" in keeping with the platform being a worldwide product.)
Jobs: a candidate can no longer be paid twice — or a single role filled twice — when an offer is accepted. For timebank jobs, two near-simultaneous "accept" clicks (or two finalists both accepting offers for the same one-position vacancy) could mint the time-credit reward twice and mark the role filled twice. Accepting an offer is now a single, atomic, all-or-nothing action: the credit is awarded exactly once, the vacancy is filled once, and any other outstanding offers for that role are automatically withdrawn. Interview self-scheduling was hardened the same way — two candidates can no longer grab the same interview slot.
Jobs: hiring-team members can now use the tools they were given access to. People added to a vacancy's hiring team (and community/tenant admins) were wrongly blocked from viewing interviews, the team list, referral stats, AI candidate ranking, analytics, the audit trail, predictions, CSV export, and from posting scorecards or running bulk actions — only the original poster could. Access now consistently follows "owner, admin, or hiring-team manager" everywhere.
Jobs: bulk candidate-status changes now behave exactly like single ones. Changing several applicants' status at once previously skipped the safeguards that single updates have: it could silently move an already-hired or withdrawn candidate backwards, wrote no history, and sent no notification. Bulk changes now respect finished states, record history, and notify each affected candidate in their own language.
Jobs: "right to be forgotten" now clears all of a member's job data. A data-erasure request used to leave behind interview notes, offer details, reviewer scorecards, status-change history, referral links, and view history. Erasure now scrubs personal data across the whole jobs module, and a problem deleting one CV file no longer aborts the rest of the erasure.
Jobs: offers and interviews can't be sent to the wrong person or a closed role. Employers can no longer send an offer or propose an interview to a candidate who has withdrawn or been rejected, or make a new offer on a role that's already filled.
Jobs: the applicant count shown on community pages is now accurate. Some pages and admin email tools were reading job-application figures from the wrong (near-empty) table, so counts and candidate lookups could be wrong; they now read the live applications data.
Jobs: abuse and runaway-cost protection. The AI job advisor, AI candidate ranking, hiring predictions, employer reviews, and the public job feeds (RSS/JSON/Indeed) are now rate-limited, and employer-review scores are validated, closing off ways to drive up AI costs, scrape data, or post malformed reviews. Internal error messages in the jobs module are now translated instead of occasionally showing English.
Jobs: a more polished, safer, fully-translated interface. Closing or reopening a vacancy and declining an interview or offer now ask for confirmation first; choosing a CV file checks the file type immediately (not only on drag-and-drop); the My Applications page reliably shows interviews and offers as you page through; browse results no longer briefly flash stale matches when you change filters quickly; the AI chat panel can be closed with Escape and works with screen readers; share text and the offer "per month" label are translated; and assorted focus-ring, reduced-motion, and empty-state details were tidied across the module.
Jobs: the employer rating control and CV downloads now work for everyone. The star-rating used to leave a review on an employer's brand page could only be set with a mouse — keyboard and screen-reader users could not rate at all; it is now a proper keyboard-operable, screen-reader-labelled control. And on the My Applications page, the "Download CV" button pointed at a private file path that returned nothing; it now downloads your CV through the authenticated endpoint, with a clear error message if it fails.
Courses: clearer feedback and a safer paid-enrolment path. Marking a lesson complete now shows an error if it does not save (instead of silently doing nothing), and the instructor dashboard's empty state reads as guidance instead of a stray "My courses" heading. Paid (time-credit) enrolment was hardened internally: the charge reads the freshest course price under the row lock, and the enrolment notification is now sent after the wallet transaction commits (shorter lock hold, no change to the charge itself).
Podcasts: stuck media uploads are now visible instead of retrying forever. If an episode's optional media-processing step kept failing, it would retry indefinitely while the episode sat silently in "pending"; it now retries a bounded number of times and is then marked "failed" (and logged) so admins can see and act on it.
Mobile: saved/unsaved state on the Exchanges screen no longer goes stale. Tapping save/unsave applied an optimistic change that was never reconciled, so after a refresh or filter change a listing could keep showing the wrong saved state (for example if it was changed on another device). The optimistic state is now cleared once the server confirms it.
Audit follow-ups: podcast audio seeking, a duplicate-certificate guard, and smoother mobile lists. Podcast episode audio now streams with correct HTTP range support, so seeking and scrubbing in the player are reliable. A database guard (plus a race-safe issue path) ensures a learner can never receive two certificates for the same course. And the mobile Exchanges list got virtualization tuning for smoother scrolling through long result sets.
Volunteering: a volunteer can no longer be accidentally paid twice for the same hours. When an organisation approved logged hours (or a member self-logged hours with auto-pay enabled), two near-simultaneous approvals — or an accidental double-click — could pay the volunteer twice and debit the organisation's wallet twice for a single entry. Approval and payment are now strictly one-time per hours entry, guarded both in the application logic and by a database safeguard, so a duplicate payment can't happen no matter how the action is triggered.
Volunteering: organisations are now told the truth when their wallet can't cover approved hours. With auto-pay on, approving hours always said "approved and paid" — even when the wallet was empty and the volunteer was not actually paid. The confirmation now accurately reflects whether the volunteer was paid or the wallet needs topping up, and the organisation's pending list and balance stay consistent.
Volunteering: switching between two organisation dashboards no longer shows the wrong organisation's data. A coordinator who manages more than one organisation could briefly see one org's pending hours or wallet history under another org's name (and risk approving or paying against the wrong one). Each dashboard tab now always reflects the organisation you're viewing.
Volunteering: a glitch in one tab can no longer blank the whole page, and a few smaller rough edges are fixed. An unexpected value from the server could crash the Emergency Alerts tab and take the entire Volunteering page down; every tab is now isolated so a problem in one shows a contained, retryable error instead of a blank screen. The wallet deposit limit shown in the form now matches what the server actually accepts (a clear message instead of a confusing rejection), the Safeguarding and Donations toggle buttons now visibly reflect their on/off state, Safeguarding no longer shows an empty list with no explanation when data fails to load, and bulk approve/decline on applications is protected against accidental double-submission.
Volunteering: an organisation's private wallet balance is no longer exposed on its public page. The public organisation endpoint was including the internal wallet balance and auto-pay setting; these are now kept private to the organisation's own dashboard.
Volunteering: federation and rewards reliability. Federated volunteering opportunities can no longer be re-sent in duplicate to partner communities if a background job is retried, and volunteers now reliably receive the correct XP for approved hours (a matching bug could silently skip the reward for some entries).
Broker panel exchange history is now fully translated. The broker exchange-detail timeline labels ("Request created", "Provider confirmed hours", "Requester confirmed hours", "Status changed") were only ever shown in English for the ten non-English languages. They are now translated across German, French, Spanish, Italian, Portuguese, Dutch, Irish, Polish, Japanese and Arabic — restoring a fully-localized broker panel and unblocking the translation-completeness CI gate.
Podcasts: scheduled episodes stay private until their publish time, and listen stats are more honest. A future-scheduled episode could be opened early via a direct link or the audio/listen route (it was correctly hidden from listings and the RSS feed, but those routes didn't enforce the embargo); the embargo is now applied everywhere, while show owners and admins still preview as before. Separately, an episode whose related show record was missing could return a server error instead of a clean "not found"; listen analytics now clamp the reported listening time to the episode's real length so completion/retention figures can't be inflated by a client; and the per-user show-limit check no longer loads every episode and chapter just to count shows.
Scheduled podcast episodes now notify subscribers when they go live, not when they're queued. Scheduling an episode for a future time used to notify subscribers and post a feed card immediately — pointing people at an episode that stayed hidden from listings and the RSS feed until its scheduled time. A future-scheduled episode is now held back and released by a background task the moment its scheduled time arrives, which posts the feed activity and notifies subscribers exactly once (re-runs never duplicate). This completes the previously half-built scheduling feature.
Partner time-credit notifications now arrive in your language. When an external partner integration credits time to your wallet, the in-app bell and push notification were rendered in the system default language rather than yours. They now render in the recipient's preferred language, matching the accompanying email.
Landing-page builder no longer loses your place when you reorder or remove a block. In the admin landing-page builder, reordering or deleting an audience card, feature, step, or value could jump keyboard focus and in-progress edits to the wrong row, because the lists were tracked by position rather than identity. Each item now keeps a stable identity, so focus and field state stay with the right block. Saved content is unaffected.
Smaller robustness and accessibility fixes. Disconnecting a social-login provider no longer surfaces a raw internal error message on failure (it now shows a clear, localized message and logs the detail server-side); the group branding colour-picker's hue slider now has a translated screen-reader label; a shared data-loading hook now ignores a slow earlier response after its input changes, preventing brief stale data on fast tab/filter switches; and a group team-chat screen now releases its real-time subscription when you leave it.
Site search no longer fails with a server error. On communities using the fast search engine (Meilisearch), the global search bar and its type-ahead suggestions returned a 500 server error for every query — because the listings search index was queried with an "approved listings only" filter the index had never been told to allow, and the error was left unhandled. The index now permits that filter, and as a safety net any search-engine error now quietly falls back to a database search instead of failing the whole request, so search can never hard-fail this way again. (Operators: re-run the search-index sync after deploying so the live index picks up the new filterable attribute —
php scripts/sync_search_index.php --all-tenants.)The "Leave a review" email link no longer hits a 404, and the Reviews "Pending" list works again. The review-request email's "Leave Review" button pointed at a page (
/reviews/create) that didn't exist, so it landed on a Not Found page. That route now exists and opens the review form for the right person straight from the email — and it survives signing in first, so the link works even when you're logged out when you click it. Separately, the Reviews page "Pending" tab and the dashboard's pending-reviews card were being fed the wrong data (the reviews you'd received instead of the completed exchanges you still need to review), so they never listed the right items; they now correctly show each completed exchange awaiting your review, the person to review, and a working "Write review" button.Courses module hardening (Alpha follow-up). A round of safety and robustness fixes to the new Courses module: the course builder now shows an error and rolls back the on-screen change if a section/lesson delete, reorder, or rename fails to save (previously these failures were silent, leaving the displayed curriculum out of step with what was actually stored); quiz answer keys and explanations are now excluded from course data sent to the browser at the model level as defence-in-depth (the learner quiz view already omitted them); the star-rating control now has a proper accessible label for screen-reader users; a failed review submission now shows a review-specific error message instead of an unrelated "couldn't enrol" one; and course creation now sets author identity and moderation status strictly server-side so they can never be influenced by the submitted form. New labels translated across all 11 languages.
Courses module polish (Alpha follow-up, round 2). Course counts — lessons, enrolments, and review counts — now pluralise correctly in every language (they previously used a hand-rolled scheme that left some languages, including Polish and Arabic, stuck in a single form regardless of the number); they now use proper per-language plural rules. A learner who has dropped a course can no longer post or overwrite its rating/review (only active or completed enrolments may review, so the public rating reflects genuine participants). And enrolling now ignores a cohort selection that doesn't belong to the course, instead of recording it, keeping cohort rosters and analytics clean.
Instructors can now actually read what they're grading. The course quiz grading queue previously showed each learner's submission as a raw JSON blob (e.g.
{"q12":["b"]}) with no question text — effectively unusable for grading short-answer and essay questions. It now lists each question's prompt with the learner's answer beneath it, mapping multiple-choice selections back to their option labels. The answer key is never exposed to this view. Two new labels translated across all 11 languages.Quiz attempt limits can no longer be bypassed by rapid resubmission. A quiz's "maximum attempts" cap was checked and then recorded in two separate steps, so two submissions sent at almost the same instant could both slip past the limit (a check-then-write race). The cap is now enforced atomically inside a row-locked transaction, so a learner can never exceed the configured number of attempts even with concurrent submissions.
The Prerender Engine admin page no longer crashes on the Inventory and Analytics tabs. Opening the Inventory tab threw an error ("A slot prop is required") and the Analytics tab threw "Cannot convert undefined or null to object" — both white-screened the whole admin page via the error boundary. The Inventory crash was the row-selection checkboxes being misread as a data table's built-in selection control; the Analytics crash was the backend omitting two fields from its empty response when no crawler-traffic log exists yet (always the case before any bots have visited). Both tabs now load correctly, and the Analytics view is hardened so a partial response can never blank the page again.
Device push notifications now reach every important alert. Many notifications — new messages, connection requests, likes, comments, replies, mentions, matches, achievements, job and goal updates, volunteering alerts, marketplace payouts, exchange and admin alerts and more — previously only appeared as an in-app bell or email and never arrived on your phone or browser as a push, because push delivery was incorrectly tied to the email-digest setting (which is off by default). Push is now its own channel, controlled solely by your push toggle, and fires for every notification type that warrants it on both web and mobile. It is de-duplicated, so a burst of activity on the same item (e.g. many likes on one post) can't flood your device.
"Powered By" / partner-logo image removal and freshness in admin settings now work. Removing a "Powered By" light/dark image (the × button) is now persisted on Save instead of silently reverting with "no changes saved" — the image fields are tracked in the save diff and the remove button no longer pre-clears the baseline the diff compares against. Uploading or replacing a "Powered By" or partner-logo image now also busts the cached tenant bootstrap server-side, so the footer shows the new image immediately instead of the old one until the 10-minute cache expired. (Uploads already persisted to the database; removal had no working code path, and uploads left a stale bootstrap cache.)
Footer branding now updates immediately after saving admin settings. Saving footer text, the "Powered By" label/URL, or the partner logo on the admin System Settings page now refreshes the live tenant context, so the footer reflects the new values right away instead of falling back to the default NEXUS branding until a hard page reload. (The backend already persisted the change and busted its cache; the SPA simply wasn't re-fetching its in-memory tenant bootstrap after the save.)
Changed
Module Configuration is now visible only to super-admins. The "Module Configuration" entry in the admin sidebar (under Platform Operations) — which toggles a community's core and feature modules — is now shown only to tenant super-admins (and platform/god admins). Regular admins no longer see it in the navigation.
HeroUI v3 component-usage sweep across core pages. Audited the post-login pages — Feed, Explore, Listings, Exchanges, Group Exchanges (list + create wizard), Wallet, Messages, and Dashboard — to use HeroUI v3's dedicated components instead of hand-rolled equivalents. Filter, view-mode, and split-type selectors now use
ToggleButtonGroup/RadioGroup/TagGroup; numeric fields useNumberField; member search usesSearchField; status pills useChip; the listings load-more bar usesProgress; dividers useSeparator. Also fixed several invalid interactive-element-inside-link cards (the listings save button, Explore "View Group"/"Vote Now" CTAs, and the feed composer's nested action buttons) for correct, accessible markup. No behavioural changes.HeroUI v3 component-usage sweep — round 2 (Members, Connections, Events, Groups, Volunteering). Continued the sweep across the remaining post-login modules. Quick-filter / view-mode / day-range / mood / RSVP selectors now use
ToggleButtonGroup(or standaloneToggleButton) instead ofButton+aria-pressed; the group branding picker uses the v3ColorPicker(area + hue slider + hex field) instead of native<input type="color">; the wiki parent-page picker usesSelect; remaining hand-rolled status pills useChip; and several more interactive-element-inside-link cards/CTAs were converted toButton as={Link}for valid, accessible markup. No behavioural changes.HeroUI v3 component-usage sweep — round 7 (Help, About). Converted the remaining navigation CTAs on the Help Centre and public About page from
<Link><Button>toButton as={Link}for valid, accessible markup. No behavioural changes.HeroUI v3 component-usage sweep — round 6 (Federation members/listings, Achievements). Federation member service-reach filter and listing type filter →
ToggleButtonGroup(wereChip-as-button with manualrole="button"/aria-pressed). Clickable cards on Federation listings and Achievements dropped their redundant manualrole="button"/tabIndex/onKeyDown—GlassCard'sonClickalready makes the underlying v3CardisPressable, which supplies button role + keyboard activation natively. No behavioural changes.HeroUI v3 component-usage sweep — round 5 (Notifications, Marketplace map search). Notifications all/unread filter →
ToggleButtonGroup; settings CTA →Button as={Link}. Marketplace map/list view toggle →ToggleButtonGroup(the previous buttons conveyed active state only visually — noaria-pressed— so this also fixes a screen-reader gap); added amap.view_togglearia-label across all 11 languages. No behavioural changes.HeroUI v3 component-usage sweep — round 4 (Profile, Settings, Goals, Polls, Ideation, Matches, Skills). Continued the sweep across more post-login pages. Profile/Matches/Settings navigation CTAs and back-links →
Button as={Link}(removing invalid button-inside-link markup); the Settings theme picker, Goals view-switcher, Polls category filter, and the Skills per-category selector →ToggleButtonGroup; Goal deadline fields →DatePicker; the Ideation tag filter →TagGroup; the Polls ranked-result bar →Progress. No behavioural changes.HeroUI v3 component-usage sweep — round 3 (Resources, Onboarding, Jobs, Courses, Caring Community). Resources reorder/category controls →
ToggleButton/ToggleButtonGroup; the onboarding interests/skills multi-select clouds →TagGroup; Jobs and Courses/Caring-Community navigation CTAs →Button as={Link}(removing invalid button-inside-link markup); the courses catalog and lesson-nav controls →SearchFieldandButton; remaining source/status pills →Chip. No behavioural changes."Report a problem" is now logged-in only, and de-duplicated. The floating problem-reporter (bottom-right) no longer renders for anonymous visitors, and the duplicate "Report a problem" link in the desktop footer was removed since the floating reporter already covers desktop. This stops logged-out traffic from cluttering support reports / Sentry. (Submissions were already auth-gated server-side via
requireAuthand rate-limited at 10/min; this closes the visible entry point too.)Proprietary brand names removed from the Caring Community module. All visible mentions of "KISS", "AGORIS/Agoris", "Age-Stiftung", and "Koordination und Innovation für Soziales" have been replaced with "Caring Community" throughout the frontend, admin panel, and all 11 language files. The Agoris node option was removed from the pilot inquiry form. Database table and column names, the
agoristenant slug, and internal service class names are unchanged.React upgraded from 18 to 19. Full production upgrade including React 19 concurrent features, updated type definitions (
@types/react19.x), and Vitest/testing-library compatibility fixes. All 223 usages of deprecated APIs resolved. Build, type-check, and smoke tests pass.framer-motionremoved. As part of the React 19 modernization, theframer-motiondependency was dropped and replaced with a local CSS-transition-backed shim at@/lib/motion, repointing every animation import site. Smaller bundle and one fewer heavy dependency, with no visible change to animations.HeroUI v3 migration — complete. The frontend component library migration from v2 to v3 (
@heroui/react) is finished; the v2 npm alias has been removed. All remaining components were migrated to v3 (Dropdown,Select,Accordion,Tabs,Modal,Drawer,Table,Badge,Switch,Checkbox,Radio,Tooltip,Skeleton,Slider,Popover,Pagination,DatePicker,ScrollShadow,ButtonGroup), theuseDisclosure→useOverlayStatehook adapter landed, the@heroui/stylesinternal dependency was removed, and a final wrapper/test/visual audit pass confirmed the app is clean on v3. (Supersedes the earlier "phases 1–5 of 10" status.)Admin panel is now fully translated in all 11 languages. The previous admin-English-only policy was reversed; admin UI strings now resolve through the same i18n system as the rest of the platform, and PHP locale namespaces were filled across all 10 non-English languages.
Module Configuration cleaned up. Five unimplemented orphan modules (merchant coupons, member premium, AI agents, partner API, regional analytics) were removed from the admin Module Configuration screen so admins no longer see dead toggles. The Help Centre link was also removed from the utility navbar and a duplicate AGPL footer notice was removed.
Caring Community marked as Alpha. The Caring Community module on the admin Module Configuration screen is now labelled "Caring Community Alpha" and shows an Alpha development-stage badge. Module cards support an optional
stage(alpha/beta) marker, translated in all 11 languages.
Accessibility
Search boxes upgraded to HeroUI v3
SearchFieldacross all browse/list pages. Filter inputs on Federation (listings, groups, events, members, messages), Organisations, Groups, Ideation, Marketplace (search, category, map), Messages (conversation list and in-conversation search), Talent search, Volunteering (opportunity detail and org applications), the group Files and Q&A tabs, and the Explore search entry now use the v3SearchFieldprimitive instead of a generic text input with a search icon — gaining a built-in clear (×) button,role="searchbox"semantics, and the "Search" key on mobile keyboards. (The sharedSearchFieldwrapper now also forwards a plainclassName; inputs that need an autocomplete results dropdown were intentionally left onInput.)Marketplace category breadcrumb consolidated onto the shared
Breadcrumbscomponent. The category page's hand-rolled inline<nav>breadcrumb now uses the sameBreadcrumbscomponent as the rest of the app, gainingaria-current="page", consistent label truncation, and 44px touch targets.Platform-wide WCAG 2.1 AA audit (four rounds). A multi-round accessibility campaign brought the React frontend and admin panel toward WCAG 2.1 AA: colour-contrast fixes, semantic landmarks and
roleattributes (e.g. feed cards asarticle), ARIA labels on allTabsandInputelements, accessible names on icon-only controls,aria-expandedon menus,aria-live/live regions for chat and search results,aria-busyon skeletons, keyboard support for sortable table-column headers, focus indicators, andusePageTitleon pages that were missing a browser/screen-reader title. Round 4 alone covered 24 pages and 10 admin modules. The accessible-frontend link was relabelled to reference "WCAG 2.2 AA" across all 11 languages.
Security
- Member surnames are now hidden from non-admin viewers, platform-wide. Surnames were previously returned in all user-facing API responses. They are now gated behind an admin check at every exposure point (public profile, member directory listing, and member search), so non-admins see only the first name while admins continue to see full names.
Fixed
Admin "Inactive members" report no longer crashes on load. Visiting
/admin/reports/inactive-membersrendered the "Something went wrong" error boundary fallback with the messageA slot prop is required. Valid slot names are "selection". The page wrapped a bare<Checkbox>inside a<TableColumn>for its "select all" affordance, but HeroUI v3Table(React Aria Components) only permits checkboxes in the header when they use the built-inslot="selection"— i.e. when the Table itself is inselectionMode. Replaced the manual checkbox column and per-row checkbox cell with the Table's native multi-selection (selectionMode="multiple",selectedKeys/onSelectionChangebound to the existingselectedIdsstate, rows keyed byuser_id). Also fixed a separate latent issue spotted in the same pass: the flag-type filter Select had anid="""All types" option (React Aria Collections reject empty-string ids), now'all'(omitted from the API request so backend behaviour is unchanged), and<TableColumn width={40}>(not a valid v3 prop) is gone with the column it sized. Reported via support NXR-260528-L6WQSE.HeroUI v3: profile hover-cards no longer flicker. Hovering an avatar on a feed card (and an
@mentionin post text) made the profile preview popover rapidly flash open and closed. Both are controlled HeroUI v3 Popovers (React Aria); their open state was being driven in a way that diverged from React Aria's internal trigger state —UserHoverCarddelayed the close insideonOpenChange, andMentionRendererflipped the open state instantly from raw mouse handlers — causing an open/close oscillation. Both now use a single hover-intent timer as the only opener and apply React Aria's close requests immediately, so the card opens and closes smoothly and still dismisses on Esc / click-outside.HeroUI v3: dropdown menus with selection now show a checkmark. Selectable dropdown menus (e.g. the language switcher, admin sidebar sections, feed filter menus) highlighted the chosen option internally but rendered no visible tick, because the shared
Dropdownwrapper never emitted the v3 selection indicator. The indicator is now rendered for single/multiple-selection menus only — plain action menus are unchanged (no empty indicator gutter).Welcome credits now granted when admin approves via status change. Admins were approving members by editing their status to "active" (the user detail edit page) rather than using the dedicated Approve button. The generic
update()endpoint setis_approved=1but never calledgrantWelcomeCredits, so no starting balance was applied. Fix: detect thepending → activetransition inupdate()and run the same credit-grant + welcome-email + in-app-notification flow as the dedicated/approveendpoint. Also fixed:grantWelcomeCreditswas reading thewelcome_creditstenant-settings key (which doesn't exist) instead ofwallet.starting_balance(the key the admin Settings page actually writes). The code now readswallet.starting_balancewith a fallback chain so every tenant resolves the correct value. Backfilled 5 credits to the one user who had been approved but received nothing.Member activity reports now show real data.
AuthController::login()never stampedlast_login_aton successful login after the Laravel migration, so/admin/reports/membersshowed "No active members found" for all tenants. Fixed by addingDB::table('users')->update(['last_login_at' => now()])immediately after token creation. A backfill migration approximates past login dates frompersonal_access_tokens.created_atfor all active users so reports are immediately useful.Wallet transfer/donation UX and a 404. The wallet
DonateModalexposed an unusable numeric "Recipient ID" field, now replaced with an avatar/member search picker. A missingGET /v2/wallet/configendpoint that caused theTransferModalto 404 every time it opened was added.Member profile tabs no longer collapse. A HeroUI v3 Tabs CSS issue hid all but the selected tab on member profiles; all tabs render correctly again.
Dashboard and Explore layout fixes. Restored the dashboard quick-action tiles and stopped the Explore page tabs from wrapping onto a second line.
Admin search box no longer triggers browser autofill. The browser was autofilling a saved admin email into the admin search field; this is now suppressed.
Build & infrastructure reliability. Raised the Workbox precache size limit to 5 MB (large bundles were being skipped), cast GD image dimensions to integers and guarded against
localStoragequota errors, unpinned the Redis PECL extension to fix the Docker build, added a queue watchdog cron to recover dead Horizon workers, and hardened container storage permissions.