1.6.1
Released 2026-08-17
Changed
The experimental ASP.NET backend's readiness score has been re-measured and is now 598/1000, down from 712/1000. This is a development-only backend serving no real community, so nothing a member sees is affected. The fall is a correction to the measurement, not a decline in the software — and the distinction matters. The old 712 was set when nothing existed that could compare an actual answer from the two backends; readiness was inferred from counting whether web addresses existed. A tool built this week can now ask both backends the same question and compare the replies, and on the 170 addresses the main app actually uses, only 64 give a matching answer. Scoring 87% for that would have been indefensible. Meanwhile the backend genuinely improved in the same period: the community hierarchy and the rule confining a regional administrator to their own communities now exist; three payment and identity webhooks that were silently throwing events away now refuse them so the sender retries; and the number of endpoints that reply "success" while doing nothing fell from 349 to 319. Every figure in the documentation was regenerated from live code rather than copied forward, the older score is kept as an audit trail rather than overwritten, and the score for test evidence rose from 45 to 85 because the full suite now passes on every shard in a clean automated run.
The accessible frontend's readiness score is now 920/1000, rescored against live production evidence. Two rows on the readiness scorecard had been deliberately held back in August pending real production confirmation rather than session claims. That confirmation now exists and was measured directly: the old Blade accessible frontend's code is gone from the repository, and all three accessible web addresses now serve the new frontend live. So "retire the old accessible site" moved from 5/50 to 45/50 (the remaining gap is that no person has yet done a full sign-off pass over a longer settling period), and "production deployment and rollback" moved from 87/100 to 91/100 (the per-community domain cutover is done; the open items are that switching back to a previous version has now been rehearsed off-production but not yet on the live production server, and that it is more delicate because the old quick-fallback path was removed with Blade). Live checks on both community accessible domains then closed the route and URL-shape row's final three-point deduction. No implementation code changed — this is an honest re-measurement, not new work.
Laravel is now explicitly documented as the continuing production authority, with ASP.NET retained as an optional future alternative rather than an assumed scale-driven replacement. The architecture decision records, public architecture and portability guides, repository overview, monorepo boundaries, and agent instructions now state that growth in users or traffic does not automatically justify a backend cutover. Any future ASP.NET production proposal must first prove unchanged-client contract identity, security and tenant equivalence, representative Project NEXUS performance and endurance results against an optimized Laravel baseline, operational and total-cost benefit, and a safe migration and rollback plan, followed by a separate explicit owner decision.
Fixed
The complete member-facing Irish Matches catalogue has been semantically reviewed. Every accidental
cluiche(game) andmeaitseannaform is replaced with consistentmeaitseáil/meaitseálacha; mutuality, match quality, location thresholds, notification summaries and first-person dismissal now retain their intended meaning, with only distance and percentage displays unchanged.The complete member-facing Irish Messages catalogue has been semantically reviewed. Read and sent receipts, archiving, per-user and everyone deletion, voice and automatic translation, group conversations, restrictions and coordinator-mediated contact now preserve their UI states. Safeguarding copy now says precisely that community confirmation is missing and explicitly prohibits sending DBS certificates or criminal-record information through NEXUS; a whole-catalogue gate leaves only the character counter unchanged.
The complete member-facing Irish Exchanges catalogue has been semantically reviewed. Requests, provider responses, broker approval, completion, hour confirmation, cancellation and time-credit displays now use consistent workflow language and Irish
uhour abbreviations; a whole-catalogue gate leaves only the numeric preparation-time placeholder unchanged.The complete 830-source-value member-facing Irish Settings catalogue has now been semantically reviewed. Identity verification, notifications, passwords, 2FA, passkeys, insurance metadata, safeguarding consent, GDPR rights, linked-account capabilities, accessibility, blocked users and supported-message audit controls are covered; a whole-catalogue gate limits English-identical values to two product names and three functional input examples.
Irish Settings now preserves member control throughout linked-account support. Member-approved capabilities are clearly separated from staff-recorded guardian arrangements; pending support remains inaccessible until approval, message viewing requires the member's own consent and stays auditable and withdrawable, and prepared listings or transfers remain fail-closed until the supported member agrees.
Irish Settings account-rights and destructive-action wording now matches the live controls. GDPR access, portability, restriction and permanent account deletion are distinguished accurately; password feedback now states the real 12-character minimum instead of eight, and profile, privacy, 2FA, marketing and data-request status messages restore complete accented Irish and actionable failure guidance.
Irish Settings now distinguishes passkeys from passwords throughout device setup. Windows, Mac, iPhone, iPad, Android and Linux instructions consistently use
pas-eochair, retain the real Authy product name, and clarify relying-party domains, credential limits and successful registration; focused tests prevent this security-critical terminology from regressing.The complete member-facing Irish Stories catalogue has been semantically reviewed. Story creation, camera and recording controls, reactions, viewer states and highlight management now preserve their actual product actions; a whole-catalogue gate protects the five reviewed typography and seconds-format invariants.
The complete member-facing Irish Dashboard catalogue has been semantically reviewed. Dashboard, listing, matching, activity, endorsement, exchange, review, like and comment wording now reads naturally and preserves the intended product meaning; a whole-catalogue test limits English-identical values to the five percentage, distance and XP display formats.
Five more lists on the experimental ASP.NET backend were shaped so the app would have shown them as empty. Development-only backend, not the live site. Each of these five — a member's own job applications, their membership dues, their volunteering donations, their volunteering training record, and the discount-coupon list — handed back a plain list where the real backend wraps it in a container with the list inside. An app reading the real backend looks inside that container, so against these five it would have found nothing and shown an empty page rather than an error. The wrapper is not one shared shape, which is the part worth recording: the applications and training lists carry a "where to continue from" marker and a "is there more" flag, dues carries a total count, donations carries a differently-named continuation marker, and coupons carries nothing but the list. Which one applies cannot be worked out from the response, so each of the five was read off the running real backend rather than inferred — deliberately, because over-generalising one such shape across endpoints is what caused an earlier round of 22 wrong endpoints.
The "Create event" button is no longer offered to members who are not allowed to create Events. A community can restrict Event creation to "Brokers and administrators" or "Administrators only", but the member-facing app never knew that: the button appeared for everyone, in the events page hero, the phone control bar, the empty state, the navbar create menu, the mobile quick-create sheet, the feed sidebar's quick actions, the search overlay's command list, the Explore empty state, and a group's Events tab. The refusal only arrived after the whole form had been filled in and submitted.
GET /v2/users/menow reportscan_create_events, resolved server-side by the same checkPOST /v2/eventsenforces, and every one of those entry points hides the Create action when it is false. Going to/events/createdirectly shows a short explanation and a way back instead of an unusable form. Two things are deliberate: editing an existing Event is unaffected, because that is governed by the Event policy (organiser, delegated staff, or admin) and not by the creation setting — an organiser whose community later restricts creation can still edit what they already own; and a client that has not yet seen the new field keeps the button, because the common case by far is the open default and the server refuses anyway, so a stale client costs one clear error rather than a missing feature. Both are pinned by tests that were each verified to fail against the opposite behaviour.A shared test helper's typing made supplying a signed-in user an error in hundreds of test files. The
@/contextsmock factory declared its default user as literallynull, so any test overriding it with a real user was a type error — 123 of them, recorded in the type baseline rather than fixed, which is how a new one could hide among them. Typed asUser | null, which is what it always meant. No runtime behaviour changes; the test-type baseline drops from 1,932 errors across 648 files to 1,809 across 583.Five broken tests were making the ASP.NET test suite report failures on every run. They came from two earlier changes, not from new work: three tests were checking for a response format that only applies to newer-style web addresses, on three older addresses where it never applied (and which the real backend does not even have); two more were still looking for a page-count field on lists that had correctly been changed to report "how many per page, and is there more". All five now check what the real backend actually sends. The suite is back to a clean run.
Five lists on the experimental ASP.NET backend were shaped so a page looping over them would show nothing. Development-only backend. The real backend puts these rows inside a container alongside their "is there another page" marker; the ASP.NET version returned the rows as a plain list. Any page walking the list would come up empty when pointed at the real backend. Affects the coupon list, a member's job applications, their membership dues, their donations and their safeguarding training. Notably these five use four different container shapes between them, so there was no single rule to apply — each was read from the live backend, which is exactly the check that a previous sweep skipped and got wrong.
Nine pages on the experimental ASP.NET backend were handing back raw database records; four of them were doing something worse than looking untidy. Still the development-only backend, which serves no real community. Handing back a raw record is the same mistake behind the password-hash leak below, so all nine were rewritten to choose their fields deliberately. Three turned out to be real faults rather than formatting: the group list had no filter for who may see a group, so private, secret, inactive and child groups would all have been listed once such groups existed; the "were you active this month" history was reading the wrong table entirely, returning individual point awards instead of monthly activity; and the member spotlight returned one person instead of several, always the same person, where the real backend picks at random each day. The transaction category list also skipped a switch it should have honoured.
Correction to an earlier draft of this entry: it said the transaction category list "was not filtered by community at all" and returned every community's categories. That was wrong and is withdrawn. Queries in that backend are scoped to the community automatically, by a rule applied to every table rather than written out at each query, so the list was correctly limited all along — confirmed by adding a row for another community and checking it did not appear. No data was exposed. For the same reason, the group list problem was a missing check in the code rather than anything actually disclosed: the test community contains only ordinary public groups.
A shared piece of response-handling code was itself renaming fields, breaking pages it was meant to fix. Development-only backend again. Code added recently to standardise responses converted them into a plain map along the way, which quietly discarded each field's declared name and fell back to its internal spelling — so
totalbecameTotal,per_pagebecamePerPage, and so on, on every page built that way. The affected page was correct; the shared code was corrupting it. Now fixed at source, and it makes no difference to pages that were already fine.The comparison test environment was switching three core modules off by accident. The script that turns every feature on for comparison built its list from the "features" table only, and its output replaces the whole setting — so wallet, listings and messages, which are modules rather than features, were dropped and read as switched off. That made the comparison report differences that were nothing but the two test environments disagreeing. Same shape of trap as the web-address allowlist: writing a replacement value silently throws away whatever the defaults would have supplied.
A seller's earnings panel on the experimental ASP.NET backend could show one meaningless total mixing different currencies. Again development-only, not the live site. Laravel deliberately refuses to add up money in different currencies: if a seller has been paid in more than one, it reports each separately and leaves the single headline figure blank rather than pretending euro and sterling are the same thing. The ASP.NET version added everything together and labelled the result "EUR". It also never sent the per-currency breakdown at all — which the seller's own page prefers to show — so that panel was falling back to the single figure. Both are fixed. While doing so, the response stopped including a raw copy of the seller's internal profile record, which carried their Stripe account reference and a suspension note, and had a slot for their whole user record that was one line of code away from being filled in.
A "people you may know" page on the experimental ASP.NET backend was handing out password hashes. This is the development-only ASP.NET backend, not the live site — no member was ever exposed, because this backend serves no real community. But the fault was real and serious: the suggestions endpoint returned each suggested member's entire database record to any signed-in member, including their scrambled password, their two-factor authentication secret, their email address, their email verification code, and whether they are an administrator. A scrambled password can be attacked offline at leisure, so it must never leave the server. Laravel sends seven harmless fields and the ASP.NET version now sends exactly those. The underlying mistake — handing back a raw database record instead of choosing the fields — is the same one behind an earlier member-search leak, so the new test checks the whole response text rather than a list of field names; that way a field added in future cannot slip out unnoticed. Found because the comparison fixture was given realistic content: the fault was invisible while the endpoint was being compared against an empty list.
Added
The development-only ASP.NET comparison fixture now holds realistic content, so 22 more endpoints are genuinely being compared. This affects only the throwaway test environment used to check the experimental ASP.NET backend against Laravel; no production code, and nothing a member can see. The comparison tool had been reporting 39 endpoints as "matches, but we could not actually check the contents" — the test Laravel held four users, one listing and nothing else, so lists like events, groups, posts, polls and transactions came back empty and the shape of the rows inside them was never compared. It now seeds one realistic row for each thing the React app reads (an event, a group, a repair-cafe volunteering opportunity, a poll with real options and votes, wallet transactions, a job application, and so on). Every filter those rows have to satisfy was read off the running Laravel's own query log rather than guessed, because a row that quietly fails a filter seeds nothing. Result on the 170-endpoint set: unchecked-contents fell from 39 to 17, and 20 endpoints moved into "genuinely different" — those are row-level differences that were invisible before, not new breakage.
Event Settings now has its own entry in the admin sidebar. The page holding "Who can create Events" (plus default capacity, registration, waitlist and reminder policy) was reachable only by going to Module Configuration, finding the Events card and pressing Configure — so a community that had restricted Event creation gave its admins no findable way to see or undo that. It now appears under Community, directly beneath Events, and is matched by sidebar searches for the policy itself ("who can create events", "creation policy", "event permissions", "default capacity", "waitlist", "reminders"). Translated into all eleven languages. The entry is hidden when the Events feature is off, and both the presence and the search route are covered by tests.
A safe rehearsal for the "switch back" (rollback) step of a deploy. The zero-downtime deploy can flip the site back to the previous version by swapping one Apache setting, but that specific switch had never actually been run — only configured. A new disposable rehearsal (
scripts/test/rehearse-bluegreen-rollback.sh) stands up a throwaway web server and proves, using the real deploy code, that: the switch is accepted and moves live traffic; switching back works; a rollback to an older version that predates the accessible site still starts up cleanly (a specific worry that was written down but never tested); and a broken configuration is rejected and the previous good one restored automatically. All 11 checks pass. This tests the logic, not the exact production server, so a final one-line check on the live server at cutover is still sensible — but it is no longer a leap of faith.A safe, scheduled way to run that final check on the real server. A new helper (
scripts/deploy/verify-prod-apache-rollback-configtest.sh) lets the deploy operator confirm the same behaviour on the production server in one command. It only checks the configuration (it never reloads, restarts, or switches traffic), tests both the normal and the pre-accessible-site states, and restores the config file automatically — even if interrupted. It is scheduled as a required gate for the next deploy window in the release runbook, with a result field to fill in afterwards. Nothing runs against production until the owner authorises that window.
Fixed
Creating an Event was refused on any community other than the admin's own, even when that community allowed all members to create Events. The refusal read "your community only allows authorized staff to create Events" while the community's own Event Settings page correctly showed "All active members" — so the message named a restriction that was not in force.
EventConfigurationService::canCreate()looked the acting user up withWHERE id = ? AND tenant_id = ?against the Event's community and returned false on a miss, and that miss was evaluated before the "all members" short-circuit. Any actor whose account row lives on a different community than the one they are acting in was therefore refused: platform admins, network (is_tenant_super_admin) admins, and anyone administering a sub-tenant. Confirmed on production against thepartner-democommunity (a sub-tenant) by an admin whose only account row is on another community. The actor is now resolved by authenticated id alone — auth is global, only resources are tenant-scoped, and the id comes fromrequireAuth(), so there is no IDOR risk — and the admin-tier decision for the two restricted options now uses the canonicalAdminTierpredicate instead of a partial inline role list, which also fixessuper_admin/godrole strings andis_adminbeing ignored there. The restricted options are unchanged in effect: brokers still count as staff, coordinators still do not, and suspended or soft-deleted accounts are still refused. Ten regression tests assert the service directly, and were verified to fail against the old lookup.Status words on the accessible frontend showed in English to members using another language, and the translations for them already existed. A sweep of every place the accessible frontend keeps a list of display words found 43 such lists holding 187 English words — and 183 of those 187 already had a proper translation sitting in the language files, in most cases written specifically for that list and then never connected up. Two ways it went wrong: some lists were printed straight out in English, and others were translated only when the page happened to hand the translator in, falling back to English when it did not. Now fixed and checked by rendering the pages in German: the status of a course you teach, the status of a course you are taking, podcast show and episode status, and a marketplace item's condition, delivery method and pricing — the last three appear on search results, which every member sees. Two podcast messages were falling back to something worse than English: the internal name of the translation itself. The fragile "only translate if a translator was passed in" wiring has been removed from those pages entirely, so no path is left that can fall back. Not finished, and worth stating plainly: 36 lists holding 161 English words remain. Most of those translate first and only use English if a translation is genuinely missing, so they are a latent risk rather than a live one — but 57 places across six other pages still use the fragile wiring that caused the podcast leak, and the marketplace advanced-search filters are still a block of English. Those have not been checked page by page and are not fixed.
Group exchanges could not be started on the accessible frontend, which left them stuck and nobody notified. A group exchange is one where several members swap time at once. There was no way to start one, so it stayed a draft for ever — and starting it is the only thing that tells the people taking part that they need to confirm their hours. So they were being waited on without ever having been asked, and a member looking at the same exchange in the main app saw no way to respond either. Nothing was wrong on the server; the button and the connection to it simply did not exist here. Fixing it uncovered three more faults in the same workflow, all corrected together because starting is no use without them: (1) two of the statuses the page checked for — "pending" and "approved" — are not real statuses at all, and the real one that means "still gathering people" was not checked, so an exchange in that state could be neither edited nor started; (2) the page asked people to confirm an exchange that had not started, and their confirmation could not lead anywhere; (3) four of the eight statuses had no translated wording and appeared in English in all eleven languages — three now reuse the wording already written and reviewed for one-to-one exchanges, so both kinds of exchange describe the same situation the same way, and the fourth was newly written in all eleven. Cancelling stays available at every stage, including when an exchange is in dispute. Two existing tests had recorded the broken sequence as correct and have been corrected.
Items priced in either money or time credits hid the money option everywhere on the accessible frontend. Some marketplace items can be bought two ways — pay cash, or pay in time credits — and the buyer chooses which. Every item card in every list showed only the time-credit price, so a member browsing could not see they had the option to pay with money, and the price then changed when they opened the item (the item page had it right). The cause was two separate pieces of code doing the same job, only one of which knew about two-way pricing; there is now one, so the card and the page cannot disagree again. The same fix corrects the colour-coded price tag on the search results page, which always said "priced in time credits" regardless. Alongside it, three pieces of wording that had been written into the code in English — the words "time credits" on every price, and the label "Free" — now come from the translation files and appear in the member's own language; a fourth string, the two-way price itself, was going through a translator locked to English and now follows the member's language too. The new "time credits" wording for each language was taken from that language's existing reviewed translation of the two-way price, so nothing was machine translated. One existing test had recorded the fault as correct and has been corrected. Separately noted for a translator rather than changed here: the German word currently used for "Free" means free-as-in-unoccupied rather than free-of-charge.
Six places on the accessible frontend saved the wrong thing and told the member it had worked. None of them showed an error, which is why they had gone unnoticed. (1) Editing any discount coupon quietly widened it to the seller's entire catalogue, because a scope value the form never displays was being resent on every save; it is now sent only when the coupon is created. (2) Adding someone to a group exchange that splits hours by share silently ignored the hours typed in and reset the share to one, which also moved everyone else's share; the number entered is now used as that person's share. (3) An organiser could complete a group exchange that was under dispute, which moved the time credits; Complete is no longer offered on a disputed exchange, while Cancel remains. (4) Buying an item marked for community delivery saved no delivery method at all, so the order could never be picked up by a volunteer driver. (5) The buyer's "Active" orders tab was permanently empty and the "Completed" and "Cancelled" tabs each hid a whole category of orders, because the tab names were being sent to the database as if they were order statuses. (6) A donation to another member that failed sent the member back to the community fund form with their amount already filled in — so pressing Donate again gave those time credits to the fund instead of the person. It now returns to the right form with the recipient, amount and message intact, and pins the recipient to the person actually chosen. Each fix has a regression test naming the wrong write it prevents; six existing tests that had recorded the faulty behaviour as correct were corrected alongside.
Listing cards showed "User" instead of the member's name. The listings index endpoint (
GET /v2/listings) never sentauthor_nameorauthor_avatar, although the listing detail, saved and featured endpoints all do. The React listing card readauthor_nameonly, so every card in the grid and list views fell back to the generic "User" label.ListingService::formatListingItem()now emits both fields (organisation accounts use their organisation name, as elsewhere), and the card also falls back touser.name. Regression tests cover both ends.Long addresses broke the listing card footer. In the grid view the member row and the hours/location/distance row shared one line, so a long address wrapped onto a second line — squeezing the member's name to an ellipsis and leaving uneven space at the bottom of each card. The footer is now two fixed rows: the member above, the meta below, with the address truncated to a single line and the full text available on hover. Card footers are the same height regardless of address length. The list view's address now truncates at a wider limit and also carries the full text on hover.
Irish and WCAG 2.2 coverage now reaches the real browser gates. React's blocking accessibility suite explicitly runs axe's WCAG 2.2 AA rules and exercises Irish sign-in plus five core authenticated member routes. The Irish journeys also prove the application's large-text mode at a 320 CSS-pixel viewport without horizontal overflow, while a forced-colours login journey checks native high-contrast semantics. The accessible frontend now browser-tests Irish sign-in, registration and the accessibility statement for Irish document metadata, exact catalogue headings, narrow reflow and serious or critical axe violations. A static CI contract prevents these matrices and profiles from being silently removed.
Irish localisation is now quality-gated across both maintained frontends without Google Translate. The accessible frontend's course, federation, commerce, volunteering, event and safeguarding catalogues no longer contain unreviewed English fallbacks, broken question marks or misleading literal terms. React's Irish admin, federation, safeguarding, notification, account-support and commerce copy has also been repaired, including corrupted accented text and clearer wording for logging in as another user, vetted-member restrictions, read-only access and burnout. A shared terminology glossary and CI audits now block these regressions and explicitly prohibit Google Translate for Irish.
React's remaining English-identical Irish values are now exhaustively reviewed and pinned. Sixteen genuine UI gaps across administration, help, caring-community and social catalogues now use authored Irish, while all 737 intentionally unchanged names, identifiers, examples and technical formats are recorded by exact key, value and review reason. The Irish audit rejects any new unclassified English fallback, changed invariant or stale exception; Google Translate remains prohibited, and the accessible frontend's generated Irish catalogue remains fully synchronized with its Laravel source.
Irish and accessible-frontend documentation now matches the measured implementation. The public index no longer claims that the deleted Blade frontend still exists, current-status links point to the canonical 920/1000 Web UK scorecard, React's 737 reviewed invariants and Web UK's 9,348-key Irish integrity result are documented separately, and historical statements that Irish blocked character-count, date, timeout, federation, or event-validation work are explicitly superseded without rewriting the audit trail.
The complete 24-value React Reviews namespace has received Irish semantic review. Average ratings, anonymous reviewers, review deletion, empty states, completed exchanges, review recipients and star accessibility text now retain their actual meaning; malformed wording that referred to recruits or simply “more” reviews has been replaced. A whole-catalogue gate rejects English residue and corrupted values.
The one-value React Endorsements namespace has received Irish semantic review. “Most Endorsed” now uses the Irish verb for endorsing rather than incorrectly saying that the greatest number of people agreed, and a complete-catalogue test locks the distinction in place.
The complete 39-value React Connections namespace has received Irish semantic review. Connection nouns no longer use an imperative verb, disconnect actions no longer imply dissolving something, empty and search states read naturally, and sent-request outcomes now preserve acceptance, refusal, cancellation and link-removal meaning. A whole-catalogue gate rejects English residue, whitespace and invisible corruption.
The complete 289-value React Community namespace now has a whole-catalogue Irish semantic-review gate. Every English source value is covered, all translated values are checked for surrounding whitespace and invisible corruption, and the only permitted exact-English values are the three functional email/URL examples. This closes the Community namespace review rather than relying on aggregate key coverage.
The remaining 26 Irish Community strings for recommended shifts, impact certificates and page labels have completed semantic review. Personalised recommendations now refer naturally to shifts and percentage compatibility; recommendation and certificate failures retain their retry action; certificate copy clearly distinguishes approved volunteer hours, verification and generation; and closing-date and registration-page labels now read naturally.
Irish Community emergency-shift alerts and group sign-ups have completed a 35-string semantic review. Alert urgency, pending requests, expiry dates and failures now use complete member-facing language. Group reservations clearly explain volunteering together, invitation delivery and invalid-email failures without importing English singular-they grammar; the example email remains unchanged deliberately.
Irish Community organisation discovery, registration and detail pages have completed a 109-string semantic review.
Eagraíochtterminology is now consistent; loading, empty and retry states are complete; registration distinguishes administrative approval from generic permission; and public-listing terms state the organiser's authority and responsibilities clearly. Opportunity counts follow Irish numeral grammar, while applications and organisation reviews now distinguish reviews from administrative inspection and translate the remaining Rating and Comment labels.On the accessible frontend, 25 form and messaging texts now show real Irish instead of English. The validation messages, the sign-in/password-reset prompts, the password-strength meter wording, and several direct-message labels had been added in English for Irish while the other ten languages got proper translations. A translation-quality check (which the earlier commits had not triggered) flagged them; they are now translated into Irish, matching the other languages. This also clears a failing check on the shared build so the whole build passes again.
On the accessible frontend, the date-and-time fields on events, the event agenda, podcasts and marketplace pickup slots now work again. The shared helper that reads and re-displays those "date + time" fields was missing its time-handling half in the committed code, so several organiser pages could error out. The helper is now complete, so those pages load and save correctly.
On the accessible frontend, the courses pages now show their status messages in the member's own language. The pop-up confirmations and errors on courses (for example "You are now enrolled", "Lesson marked as complete", "Your course was published", "You do not have enough time credits to enrol") — about 40 messages — and the eight labels on the course-analytics page ("Total enrolments", "Completion rate", and so on) were hard-coded English shown to everyone. They are now translated into every language (Irish falls back to English for now, as agreed).
On the accessible frontend, the direct-messages page and the group-delete confirmation now appear in the member's own language. On a one-to-one conversation, the "Regarding:" label, the "Attachments" heading and its hint, the fallback names ("Unknown", "Listing", "You"), and the screen-reader label for the message list were all hard-coded English; so was the "confirm you understand the group will be permanently deleted" error on the group-delete form. All are now translated into every language (Irish falls back to English for now, as agreed).
On the accessible frontend, more messages now appear in the member's own language instead of English. The federation status messages (connected, left the network, request sent, message sent, and their failure notices), the "enter your community code" / "enter a new password" / "password must be at least 8 characters" errors on the sign-in and password-reset forms, and the "you do not have permission" title on a blocked marketplace page were all hard-coded English. They are now translated into every language (Irish falls back to English for now, as agreed).
On the accessible frontend, a set of smaller rough edges have been smoothed. Opening a single post by its link no longer logs you out if your sign-in just expired (it quietly refreshes, like other pages). A poll page still loads if its comments briefly fail to load. Trying to leave an event waitlist now only says "done" when it actually worked (while "you were already off the list" still confirms cleanly). The onboarding "add a bio" step no longer blames your bio for an unrelated hiccup. A job's applications page shows a proper "service unavailable" message rather than "Forbidden" during an outage. A linked account that has only an email address now shows that email instead of "Unknown member". Two small accessibility tidy-ups: the footer's link groups are now a proper navigation landmark, and the organisation-name field offers autofill. Covered by tests.
On the accessible frontend, the federation "opt-in" page no longer loops for a community that has federation switched off. If a community had not enabled federation, opening the opt-in page could bounce endlessly and end in a "too many redirects" browser error — on the very page you'd use to turn it on. It now shows the opt-in page normally. Covered by a test.
On the accessible frontend, an expired session on "Discover" or "Nearby" members now sends you to log in, and a job's own page shows the owner their controls. If your sign-in quietly expired, the Discover and Nearby member pages used to show a dead-end "we couldn't load members" box and leave you stuck; they now send you to log in like every other page. Separately, the job detail page looked up "is this my posting?" using a broken call that always failed, so the person who posted a job could be shown none of their own manage/edit controls; that lookup is fixed. Covered by tests.
On the accessible frontend, the "cancel this session" reason box on an event agenda now has a working character counter. A template typo meant the little "you have N characters remaining" counter was wired to the wrong element, so it did not work and could trip the counter's script. It now works, and a new check across every page prevents the same typo class from recurring.
On the accessible frontend, a "Back" link on the placeholder pages can no longer be pointed at another website. Those pages read a "return" address from the web link used to reach them and put it straight into their "Back" link. A crafted link could set that to an outside address (an "open redirect", useful for phishing) — and, in theory, to a script link, though the site's security policy already blocks that from running. The address is now checked and only accepted if it's an ordinary link back into this site, exactly as every other page already does. Covered by a test.
On the accessible frontend, every page view no longer makes an extra, avoidable request to the backend. Each page asked the backend afresh for the community's own settings (name, which features are switched on), even though that information barely changes. It is now remembered for 30 seconds. The care is in how it is remembered: the memory is keyed to both the community and the language, so one community can never be shown another's settings and a German visitor can never be served the English version — the two ways this kind of change goes wrong. Each page also gets its own copy, and a failed lookup is never remembered, so an unknown community or a brief backend outage cannot stick. Covered by eleven tests, including the two isolation cases.
On the accessible frontend, a member clicking a good verification or unsubscribe link is no longer told it is broken. Both pages answered "this link is invalid or has expired" for every failure, including the backend simply being unreachable or overloaded — and the verification page then suggested requesting another link, which would have failed the same way. Only the backend can judge whether a link is genuinely bad, and it now only says so when it actually has; anything else reports that we could not check right now and to try again, in the member's own language, with the unhelpful suggestion withheld. Covered by tests.
On the accessible frontend, the event organiser analytics page now reads correctly in every language. All its percentages were formatted the American way for everyone (a full stop for the decimal), its counts were ungrouped, and the "generated at" line showed a raw machine timestamp such as
2026-07-13T10:00:00Z. All three now follow the reader's language — a German organiser sees50,0 %,1.234and13. Juli 2026. The English wording is unchanged. The ideation outcomes totals, which come straight from the backend and have no upper limit, are now grouped the same way. Verified by rendering both pages in German.On the accessible frontend, an organiser can now correct an event's accessibility details — and saving a repeating event no longer erases them. Two problems, one cause. The edit form for a one-off event was missing all ten venue-accessibility fields, so step-free access, an accessible toilet, a hearing loop, a quiet space, seating, parking, transport details, an assistance contact and notes could be set when creating an event and never corrected afterwards. Worse, the form for repeating events — the one our own release notes held up as proof this couldn't drift — was reading those details from the wrong place, so it showed every answer as "not known" and, on save, wrote that emptiness back over the real information. Any organiser editing a repeating event was quietly deleting the very details a disabled member relies on to decide whether they can attend. All three event forms now read the details from the same place, through shared code, so they cannot fall out of step again. No new wording was needed in any language.
On the accessible frontend, asking for an exchange twice no longer creates two of them. Requesting an exchange moves time credits once both people confirm, and this was the only money form on the site with no protection at all — no double-click guard, no one-time token, nothing. The backend didn't check for duplicates either, so a double click, a back-button resubmit, or an impatient retry on a slow connection created two pending exchanges for the same listing, and if both were confirmed the credits moved twice. Two protections now sit on the server, so they work even with JavaScript switched off: the form carries a one-time code that is used up the moment it is submitted, and if you already have an exchange for that listing you are taken to it instead of starting another. No new wording was needed in any language.
On the accessible frontend, a seller setting a money-off coupon no longer creates one worth a hundredth of what they intended. The form asks for "the amount", but the value was passed straight through to a field the backend counts in pence — so a seller wanting £5 off typed 5, created a 5p coupon, and was shown "5.00" as confirmation, with no currency to hint anything was wrong. The amount is now converted both ways, so what you type, what is saved, and what is shown all agree, and coupon values display as real money with their currency. Percentage discounts are unaffected. Three existing tests had to be corrected because they had absorbed the fault — one of them treating a 3p coupon as if it were €3.
On the accessible frontend, two marketplace bugs that charged the wrong amount are fixed. First: if a seller received an offer of £50 and countered at £80, the page still showed them an "Accept" button — and pressing it sold the item at £50, in one click, with no confirmation. The counter amount wasn't even displayed, so neither side could see the figure actually in play. Now a countered offer is the buyer's to accept, the seller only acts on offers they haven't countered, and both people see the counter amount. Every offer button also gained double-click protection; none of them had it, and they move money. Second: the purchase page showed the price of one item while the checkout charges price × quantity — so buying 3 of a 5-credit item said "5 time credits" and took 15. The page now shows a Total, and the quantity box has its own "Apply" button so the total you see is always the amount you'll be charged. No new wording was needed in any language.
On the accessible frontend, a volunteer's accessibility needs no longer overwrite each other. If a member had recorded more than one need — say a mobility need and a separate hearing need — the page showed only one description, and pressing Save wrote that one over the other, permanently. This was on the accessibility page of the site built for disabled members, and it had been happening every time such a member saved. Each need now has its own description, its own list of what would help, and its own emergency contact, exactly as the app has always stored them. Two further ways data could vanish were closed at the same time: a need of a kind this version doesn't recognise is now carried through untouched instead of being deleted, and if someone submits an old copy of the page held in their browser, the save is now refused rather than quietly wiping everything they had entered. No new wording was needed in any language. Three tests added, one of which fails against the old code — and two existing tests had to be corrected because they had been asserting the faulty behaviour was right.
We now know how close the accessible frontend is to the React app, and it is written down. Until now the only parity figure was "707 of 707 routes matched" — which measures the accessible frontend against the old Blade site that was deleted, so anything the React app can do that Blade never did was invisible to it. Measured against React for the first time, the accessible frontend covers 71% of member-facing pages (179 of 251). Nothing React can do is missing in the other direction. Two decisions are recorded so they are not re-argued later: a platform admin panel is not required on the accessible frontend (which takes 364 staff/admin pages out of the comparison entirely), and the Caring Community module is experimental and deferred — it is switched on for 2 of the 11 communities, so the deferral is made with that known, and five specific questions are written down for the investigation. The remaining work is listed in priority order, starting with identity verification.
On the accessible frontend, a community's own pages now actually appear. If a community wrote its own page — local guidance, a policy, a campaign — the React app showed it and the accessible frontend returned "page not found", silently, with nothing in a log to notice. The page list it was reading from was empty. Those pages now load, in the member's own language, with the community's own name and heading; page content is cleaned before display, so formatting from the editor survives but anything unsafe does not. Found by comparing the accessible frontend against the React app for the first time — the existing comparison only ever measured it against the old Blade site, which never had this page either, so it could not have shown the gap. Fixing it also uncovered a fault in that comparison tool: it read this one file in a special way that missed real pages entirely, so the accessible frontend was under-reported by a page. Both fixed.
A deploy will no longer go ahead without proof the accessible frontend's tests passed. The pre-deploy safety check reads the results of the main automated pipeline — but the accessible frontend's tests, code style, branding check and accessibility checks run in a different pipeline, and the one job in the main pipeline that used to cover the accessible site was removed along with the old Blade version on 14 August and never replaced. So for three days, a deploy could have shipped the accessible frontend — now the only one, on three live web addresses — with nothing confirming its 2,428 tests had passed. The safety check now reads both pipelines. Two opposite dangers were guarded against and then actually demonstrated rather than assumed: it still lets a good deploy through (checked against the current live version, where it correctly reuses recent evidence), and it genuinely refuses when the accessible frontend's evidence is missing. The paused experimental backend that shares the second pipeline is deliberately still unable to block a deploy. Three documents that said the opposite have been corrected.
The written instructions no longer tell an operator to do the thing that would take the accessible site offline. A sweep for anything still assuming the old Blade accessible site exists found the working parts all clean — the backend, the deploy scripts, the web server template and the automated checks were all updated correctly when it was deleted. The problem was the documents. The deployment guide's routing table said the accessible web address is served by the PHP application, and the custom-domains guide contained a web-server snippet pointing at the PHP port; anyone rebuilding a web server entry from either would have taken the live accessible site down. The document all the others defer to contradicted itself in four places, still describing the changeover as half-finished. The instructions file that AI coding assistants load automatically still said the accessible frontend "is not deployed yet" and pointed at a deleted folder as the thing to copy — which is how an assistant ends up refusing correct work or hunting for files that no longer exist. The front-page architecture diagram also had a broken leftover box. All corrected against live measurements taken today, with the old wording kept and dated so nobody restores it by mistake.
A routine deploy can no longer forget the accessible frontend, and it now says so before pushing. Including the accessible frontend needed a
--with-webukflag, and leaving it off means the accessible addresses have nothing to fall back to since the old Blade site was deleted. The live server already refuses such a deploy (a marker file recorded the August cutover, verified present), but it refuses only at the last step — after your code has been pushed and after the full wait for GitHub's checks. Including the accessible frontend is now the default, and excluding it has to be typed out as--without-webuk, which prints exactly which addresses that would take offline. Separately, a check in the deploy test file had been quietly failing since 14 August, because it searched for a warning about "falling back to Blade" that was deliberately removed when Blade was — the code was right and the test was out of date, which is the dangerous way round.Both community accessible web addresses are now monitored, and the monitor now checks which service answered. Only the main accessible address was being probed every 15 minutes; the two community accessible domains have been serving members since 14 August with no up/down check, so a fault affecting just one community's own address would not have raised an alert. Also, the check named "not a silent fallback" only ever compared status codes — and the React app answers "200 OK" on any address, so a misrouted domain could have looked healthy while showing members the wrong application. Each check now confirms the response identifies itself as the accessible frontend. Proven both ways: all nine checks pass against the live sites, and the new check correctly reports a failure when pointed at the React app.
On the accessible frontend, the "this page has expired" tab title is now translated. When a form session times out, that page's heading and body already appeared in the member's language, but the browser tab — and what a screen reader announces as the page name — stayed in English. Every other error page (not found, forbidden, too many requests, file too large, service unavailable) already did this correctly, so this was a single missed line rather than missing translation; the wording already existed in all eleven languages.
On the accessible frontend, five more create forms now keep what you typed if the submission is rejected. When a new idea, campaign, course, volunteering opportunity, goal or poll was rejected (for a validation problem or a backend hiccup), the form came back blank and everything had to be retyped. All of these now re-display with your entered text — title, description, options, dates and the rest — so you can fix the one problem and resubmit. This matches the behaviour the wallet, jobs and events forms already had. Covered by tests.
Irish Community waitlists and volunteer wellbeing have completed a 59-string semantic review. Queue membership, position loss, open-place notifications and retry failures now use complete actions. Mood check-ins, wellbeing risk, burnout, suggested rest and self-care guidance now distinguish ordinary tiredness from exhaustion, replace malformed self-care wording, and describe feeling overwhelmed naturally.
Irish Community credential verification and shift swaps have completed a 60-string semantic review. Credential states now distinguish verification from generic confirmation, expiry copy no longer uses a broken
credential(s)construction, police/background checks remain jurisdiction-neutral, upload limits and retry actions are complete, and shift-swap requests consistently identify shifts, sent/received states, acceptance, refusal and failures.All 188 member-facing Irish Caring Community values are now covered by a completion gate. Trust levels, caregiver wellbeing, respite cover, care providers, emergency alerts, Federation discovery, onboarding, support relationships, data export and the Warmth Pass retain their reviewed meanings; only the Swiss
Spitexservice name may remain identical to English. The separate 707-value staff panel and administration surface remains queued for the administration phase.Irish Caring Community emergency alerts and Federation discovery now use the intended member actions. Dismissing an alert is a clear close action, the emergency severity is a noun rather than a dangling adjective, and Federation community discovery uses the platform's established
Cónaidhmterminology with a natural manual-slug fallback and count label.The complete 671-source-string Irish Legal catalogue has now been semantically reviewed. The final 67-string Trust & Safety pass removes English fallbacks and Ireland-only emergency advice, restores the global local-emergency-services instruction, translates cross-region Federation, background-check and vetting guidance, and clarifies what the platform verifies, stores, insures and does not guarantee. Exchange steps, first-meeting precautions, disputes, member responsibilities and RGCS rights now use natural Irish. A whole-catalogue gate permits only seven reviewed Sentry, Pusher and browser product names to remain identical to English.
The complete 105-string Irish Community Guidelines and Acceptable Use Policy have received semantic review. Respect, identity, privacy, in-person safety, confidential reporting, proportionate enforcement and independent appeals now read naturally and retain their safeguards. Fraud, impersonation, fake time-credit activity, non-consensual sexual content, intellectual property, spam, artificial engagement, platform interference, account security and serious-breach reporting are repaired; both documents now describe themselves as clear Irish rather than “plain English.”
The 44 Irish strings for legal acceptance, custom documents, Swiss data-protection consent and Legal page metadata have completed semantic review. Updated-document notices now distinguish singular and plural review actions, the consent statement names the actual Accept control, and policy names match the reviewed Legal documents. The Swiss FADP notice preserves explicit consent and the choice to retain basic features without AI, while search metadata no longer describes Irish guidance as “plain English.”
The 58 strings identifying the Project NEXUS platform-provider legal documents have completed their Irish semantic review. Platform terms, privacy and disclaimer navigation now uses consistent policy names; the provider notice remains explicitly separate from each community operator's terms; the authoritative-English notice retains its legal effect; and controller/processor, RGCS rights, liability, operator responsibility and governing-law headings use accurate Irish. A focused gate covers all four platform sections.
The Irish Legal hub and document version history have completed their 57-string semantic review. Policy cards now use the same natural terminology as their reviewed documents, distinguish community policies from Project NEXUS platform-provider documents, and describe Trust & Safety boundaries in plain language. Revision history now uses fully accented Irish for documents, effective dates, summaries, comparisons and change counts, with a focused gate covering both sections.
The 45-string Irish Accessibility Statement has completed its semantic review. The commitment, keyboard, visual, screen-reader and responsive-design guidance now uses fully accented Irish; partial WCAG 2.1 Level AA conformance is explained accurately; and feedback, technical specifications, browser and assistive-technology recommendations preserve their intended meaning. A focused gate prevents the previous corrupted and accent-stripped wording from returning.
The standalone Irish Cookie Policy has completed its 80-string semantic review. Required, analytics and preference categories; consent storage; expiry periods; Sentry fault reporting; optional performance and session replay; third-party controls; and browser-management instructions now use fully accented Irish and preserve the actual consent boundary. Pusher and Microsoft Edge are no longer translated as ordinary words, CSRF and JWT purposes remain technically accurate, and disabling required cookies clearly says that sign-in and core features will stop working; seven browser and service product names remain unchanged by design.
The Irish Legal catalogue's Terms of Service and Privacy Policy core has received a complete semantic repair pass. The 214 strings covering time-credit rules, account responsibilities, prohibited conduct, safety, liability, termination, collected data, processing purposes, profile visibility, security, GDPR rights, cookies, retention and safeguarding metadata now use fully accented, natural Irish. Material mistranslations are repaired: discrimination is no longer “ideology”, spam is no longer “drilling”, impersonation is no longer “fake personality”, legitimate interest and data-device terminology are restored, and members' access, correction, erasure, portability, restriction and consent rights retain their legal meaning. The remaining Legal sections are still under review and are not represented as complete.
On the accessible frontend, a rejected profile save no longer throws away everything you typed. If a profile edit was rejected (for example, a blank name, or a backend hiccup), the page quietly reverted to your old saved profile — so a long "about me" bio, your tagline, phone and location all vanished and had to be retyped. The form now keeps what you entered when it re-displays, so you can fix the one problem and save again. Covered by a test.
On the accessible frontend, the donations dashboard "total raised" no longer shows 0.00 once real money comes in. The total was added up from the on-screen formatted figure rather than the underlying number, so as soon as an amount reached the thousands — or in any language that writes decimals with a comma (German, French, Spanish, and others) — the sum broke and displayed as 0.00. It now adds up the real amounts, so the figure is correct in every language and at any size. The same fix was applied to the volunteering-expenses totals. Covered by a test.
The complete 353-value member-facing Irish Ideation catalogue has been semantically reviewed. Challenge discovery, idea submission, drafts, voting, comments, favourites, evaluation guidance, attachments, campaigns, templates, project-team conversion, outcomes, tasks, documents and chat channels now use consistent accented Irish. The review removes extensive accent-stripped and malformed copy, distinguishes comments from notes, voting from withdrawal, attachments from generic media, and ideation from literal “idea composition”; destructive confirmations and implementation outcomes now state their effects clearly, with only the two functional URL inputs unchanged.
On the accessible frontend, the unread message and notification counts in the header can no longer briefly show another member's numbers. To avoid re-asking the server on every page, the site keeps each member's unread counts in memory for a few seconds under a key built from their sign-in token. That key was taken from the first part of the token — but for the kind of token the platform issues, that first part is the same for every member on every community. So for a few seconds after one member loaded a page, the next member using the same server could see that first member's unread counts in their header (the numbers only, never the messages themselves), and clearing one member's cached counts wiped everyone's. The key is now a unique fingerprint of the whole token, so each member's counts stay their own. Covered by a test.
The complete member-facing Irish Goals catalogue has been semantically reviewed. Private and community goals, templates, progress check-ins, reminders, history, milestones, streaks and goal-buddy support now consistently use
sprioc,clárú isteach,meabhrúchánandminicíocht. Corrupted accents and template wording are repaired, the untranslated software term “cadence” is removed, overdue status refers to the goal deadline, and gentle buddy nudges remain distinct from scheduled reminders; a whole-catalogue gate leaves only two percentage displays unchanged.The complete member-facing Irish Profile catalogue has been semantically reviewed. Profile identity, biography, skills, listings, achievements, availability, connections, reviews, blocking, activity and accessibility labels now use natural wording consistent with the reviewed member journeys. A biography is no longer described as biology, availability is no longer hospitality, retry controls are actions rather than trials, blocking copy is gender-neutral, verified identity has clear agency, and NexusScore remains an established product name; a whole-catalogue gate limits English-identical values to two functional display tokens.
The complete member-facing Irish Wallet catalogue has been semantically reviewed. Transfers, donations, community-fund balances, transaction history, exports, ratings and the venue pass now use natural financial and time-credit wording. The maximum-transfer warning no longer contains the Irish word for weather/time, donation actions consistently describe giving credits to a person or fund, hour amounts work without brittle English plurals, and screen-reader transaction summaries retain their direction and amount; a whole-catalogue gate leaves only the numeric donation placeholder unchanged.
On the accessible frontend, donating time-credits is now as protected against accidental double-sending as transferring them. Sending credits to another member already guarded against a double-click, a slow retry, or pressing back-and-resubmit sending twice. Donating — which moves credits out of your wallet in the same way — did not: the main donate box stopped a rapid double-click, but a back-button or slow-connection retry could still donate twice, and the donate form on the wallet management page had no double-click guard at all. Both donate forms now carry the same one-time key transfers use (so the server can ignore an accidental repeat) and both have the double-click guard. Covered by tests.
On the accessible frontend, the live password-strength message when signing up or resetting a password now appears in the member's own language. As you type a password, a message updates ("Use 12 or more characters…", "Checking against known data breaches", "Strong enough", "This password appears in a known data breach…"). It is also read aloud by screen readers. It was hard-coded English, so a member using the site in Irish, German, Arabic and so on heard English in the middle of an otherwise-translated page. It is now translated into every language. (The "add N more characters" countdown became a simple "enter at least 12 characters", because a counted-down message can't be translated correctly across every language's grammar.) Covered by a test.
On the accessible frontend, a community that runs shorter sign-in sessions now gets a correctly-timed warning instead of being ignored. The "your session is about to end" warning is set up assuming a five-minute lead. If a community's server was configured with a session of five minutes or fewer, the code quietly ignored that and pretended the session was the usual 30 minutes — so a member on a genuinely short session would be signed out by the server with no warning, or a warning still counting down. The warning lead is now always fitted inside whatever session length the server reports, so the warning fires before the session ends regardless of how short it is. (In practice sessions are 30 minutes, so most communities see no change.)
A small hardening note recorded in the code: the accessible frontend's helper that turns typed line breaks into on-screen line breaks now lives in one place with a dedicated test proving it escapes any HTML a member types before adding the line breaks — so a member cannot sneak working markup into, for example, a description. Behaviour is unchanged; this just locks the safe behaviour down so it cannot quietly regress. The reason the page style rules stay slightly relaxed (needed by the GOV.UK design components) is now written down beside the setting, with a note that the more important script protection is already fully locked.
The complete 1,358-source-value Irish Broker catalogue is now semantically reviewed and protected as a whole. Its help centre now states the platform's most important safeguarding boundary accurately: NEXUS stores only the community decision and controlled metadata, never certificates, disclosure numbers, dates of birth, document references, results or free-text evidence. The
confirmedandrevokedstatus tokens remain exact operational values, unsupported jurisdiction data fails closed, and escalation, retention, guardian assignments, police contact and troubleshooting guidance are complete. Its seven English-identical values are explicitly limited to keyboard labels,AccessNI, an em dash,ID, an example email and thekmdisplay format; a whole-catalogue gate prevents any new English fallback.Irish Broker match approval and moderation now describe decisions rather than people or machine-generated fragments. Match approvals are no longer “match approvers”; proposals use natural smart-matching wording, associated listings use
liostú, low scores mean weak matches, and approval, rejection, empty, not-found and review-date states are explicit. Shared statuses distinguish approval from generic permission. Content, feed, comment, review and report moderation now use complete action sentences, while safeguarding declaration options consistently use the platform's establishedcosaintterminology.Irish Broker configuration now preserves the difference between tenant-wide policy and broker-operated thresholds. The panel no longer leaves
tenantin English, and admin-only settings identify their audience clearly. Message-copy rules, listing exchange requirements, automatic low/high-risk handling, maximum hour variance, exchange timeouts, approval validity, vetting and insurance enforcement, new-member monitoring, random sampling, archive retention and high-risk notifications now describe the actual setting rather than word-for-word machine approximations. The public example email remains unchanged deliberately.Irish Broker review archives now describe immutable compliance evidence accurately. Archived message copies are
inléite amháinrather than already read, and conversation snapshots are preserved views rather than photographs. Listing, flag, decision-maker, decision-note and sent-date labels are corrected; the filter is no longer phrased as an instruction to review it; and frozen-record guidance clearly says the record is retained exactly as reviewed and cannot be changed.Irish Broker risk and insurance controls now retain their fail-closed and verification meaning. Risk tags consistently apply to
liostuithe, distinguish approval from permission, and explain that retired role-vetting requirements remain safely closed because messaging contact attestation cannot satisfy them. Risk levels, financial categories and empty/error states are corrected. Insurance creation, verification, rejection and deletion now use completed-action wording; “verified at” is no longer confused with “verified by”; expiry labels use real Irish day units; and certificate types and retry guidance are natural.Irish Broker message review and member monitoring now distinguish flags, decisions and restrictions consistently. Flagging is no longer blurred into generic marking, message-copy metadata separates who reviewed a record from when it was reviewed, and archive labels no longer present nouns as commands. Approval-and-archive controls describe both actions, high-risk references use
liostú, and queue counts and empty states are grammatical. Monitoring now identifies members under oversight, removes a machine-generatedg(h)gender placeholder, and states expiry, messaging restrictions, failures and retry actions directly.Irish Broker member details and exchange decisions now say exactly what staff will change. Verification and password-reset actions identify the email being sent, two-factor reset and profile changes use completed-action wording, and time-balance guidance explains positive credits and negative deductions rather than presenting incomplete accounting nouns. Exchange controls consistently distinguish approval from generic permission, use
liostú, display hours as hours of time, and provide grammatical success, rejection, empty, history and queue-failure messages.Irish Broker vetting now states the privacy boundary and community decision accurately. It instructs staff to record only operational scope and the internal decision, and explicitly forbids uploading or pasting certificates, certificate numbers, disclosure results, identity documents or criminal-record information into NEXUS. Policy packages, attestation schemes, private notes, review dates, revocation outcomes and resolved member contact now use natural labels; a corrupted certification heading and an English “Close” control are repaired, while
AccessNIremains protected as a scheme name.Irish Broker member approval, onboarding and safeguarding controls now preserve the action being taken. “Never logged in” is no longer an instruction never to log in; bulk approval, suspension and selection buttons use real imperatives; confirmations name the member rather than importing English singular-they grammar; and success notices say what happened. Onboarding consistently uses
ionduchtú, its funnel distinguishes first listings and returning members, and safeguarding tabs distinguish flagged messages, guardian assignments and recorded member preferences with natural error and empty-state wording.Irish Broker navigation and its working dashboard now describe safeguarding work rather than machine-translated approximations. Match approvals are no longer labelled as people who approve matches, onboarding flags are no longer sign-ins, and moderation is no longer left in English. Pending-review descriptions, partial-metric failures, message flags, listing risk tags, plural work counts and compact elapsed times now retain their operational meaning. A focused semantic gate protects the shell and dashboard while the rest of the 1,358-value Broker catalogue remains under review.
The complete 562-source-value Irish Federation catalogue is now semantically reviewed and protected as a whole. Reputation and review text now puts counts before Irish nouns, distinguishes Federation reputation clearly for assistive technology, and uses natural verification wording. The final opt-in notice no longer says that opting out is required. Its six remaining English-identical values are deliberately limited to two
kmunits, the compact estimated-hours format, and three score/count display patterns; a catalogue-wide test prevents any new English fallback.Irish Federation listings, settings, events, groups and member transfers now use complete, natural instructions. Listings consistently use
liostuithe; settings no longer describe a disabled feature as a disability and now preserve the grammar of dynamic enabled/disabled notices; service range distinguishes remote availability from willingness to travel; event and group counts put their numbers in the correct place with Irish mutations; and external-member profiles, pending transfers and successful credit amounts no longer contain malformed or English wording.Irish Federation member discovery and messaging now preserve explicit opt-in and communication meaning. The member directory uses natural service-range, remote-work, travel, loading and profile actions. Messaging no longer translates “opt in” as “opt out” or describe joining as being towed into the Federation; composing a message no longer uses a poetry verb; and automatic translation, delivery status, reply fields and change actions now state exactly what the controls do.
Irish Federation connection and onboarding controls now preserve consent, visibility and status choices. Connection-request tabs and outcomes use complete Irish; declining a request is no longer translated as economic decline; and unavailable partner pages clearly identify what could not be found. Federation setup now presents profile visibility, search, skills, location, reviews, messaging, transactions, email notifications and travel range as deliberate controls, with natural on/off states and service-area wording.
Irish Federation discovery now describes partner communities, shared services and network status accurately. The hub and partner directory consistently use the genitive form of the Federation and
liostuithe; partner-community counts put the number before the member noun with Irish mutations; service exchange says members use their own time credits; and empty, loading and detail actions are complete. Disabling federation no longer uses disability terminology, while partner dates no longer translate “since” as “because.”The complete 769-value React Marketplace catalogue has received a contextual Irish review. The final pass repairs free-item giving, group listings, hybrid cash-and-time-credit pricing, AI reply suggestions, map discovery, approximate locations and Stripe seller setup. Listing results no longer use generic list terminology; onboarding consistently describes account and payment setup rather than workplace induction; payout and identity requirements preserve their financial meaning; and a whole-catalogue test permits only seven reviewed numeric, distance, counter and placeholder formats to remain language-neutral.
Irish Marketplace community delivery, pickup and stock controls now explain fulfilment clearly. Community members offer to deliver an item in exchange for time credits, with the buyer or seller reviewing the offer; pickup screens distinguish collecting an order from organising a collection and use natural pickup periods rather than software slots or windows; and inventory guidance explains that stock tracking prevents selling more items than are available. Low-stock badges now say only a small amount remains rather than labelling the inventory itself as low.
Irish Marketplace collections, saved searches and promotions now describe their actual member actions. Collections organise the items a member likes rather than generic favourite market goods; empty and failure states use complete sentences; saved-search alerts explain that newly listed items match the search; and the activation control says it turns an alert on or off. Promotion choices consistently refer to a
liostú, replace feathered-listing wording, and identify the homepage carousel rather than a car show.Irish Marketplace seller and owner pages now keep listing ownership, renewal and negotiation actions clear. Seller profiles, editing, My Listings and My Offers consistently use
liostú; renewal and removal outcomes identify the listing; destructive confirmation says the action cannot be reversed; and empty states speak directly to the seller. Counteroffers now use one unhyphenated term and a monetarysuim, while starting checkout for an accepted offer is correctly described as beginning payment rather than inspecting an exit.Irish Marketplace creation and discovery now use natural listing, condition, delivery and filter language. The Marketplace hub and category pages consistently describe
liostuitherather than generic lists; featured items are no longer described as feathered or explicit; listing creation has clear upload limits, validation, AI-description and publishing actions; and search filters distinguish collection from sending an item without calling ordinary shipping navigation. Relative dates, saved-item feedback, empty states and compact timestamps now read as complete Irish phrases instead of title-cased or invented abbreviations.Irish Marketplace listing details and offer negotiation now use consistent commerce wording. Listing pages no longer call an individual listing a generic list, describe negotiable items with a malformed word, translate featured as feathered, or expose literal HTML-video terminology. Reporting, saving, seller messages and listing counts now use one consistent
liostúvocabulary, while offer amounts, price comparisons, counteroffers and payment actions clearly preserve who is offering, responding and paying.On the accessible frontend, "please fill this in" style form errors now appear in the member's own language on the event and group forms. When someone left the title, description or number-of-places field blank (or typed too much, or a non-number), the little error that pops up next to the box was always written in English, even for a member using the site in Irish, German, French, Arabic and so on. Only the sign-in form got this right before. The event-create, event-edit, group-create and group-edit forms now show those instant errors in the same language as the rest of the page, using shared wording translated into every language. Locked by a test so new forms can't quietly regress.
Irish Marketplace checkout, delivery and order management now preserve the buyer and seller journey. Secure checkout no longer reads as a security check; shipping and discount use real commerce terms; delivery options identify couriers and local collection accurately; buyers can track purchases, confirm receipt and rate an order with natural actions; and sellers create a listing, record dispatch details and see that the buyer supplied a rating rather than that the buyer was rated. Malformed price formatting and literal checkout, auto-completion and undo wording are also repaired.
Irish shared member activity, survey, provider and story controls now describe their real actions. Dashboard activity says that a member shared a listing or post, posted about an event, or created a pobalbhreith instead of displaying noun fragments and voting terminology; satisfaction choices repair malformed Irish; provider phone and email labels use the correct contact wording; cover care is identified as replacement care; and compact story timestamps no longer invent single-letter Irish time units.
On the accessible frontend, pages that show your private information are no longer kept in the browser's cache. Your wallet, messages, profile, notifications, settings and downloaded statements are personal to you. Some of these pages did not tell the browser "don't store a copy", which meant a saved copy could, in theory, be shown to the next person to use the same device (for example by pressing the back button after you signed out). Every page shown to a signed-in member now instructs the browser and any shared cache never to store a copy, while public pages stay cacheable as before. Covered by a test.
The complete 636-value React Feed catalogue has received a contextual Irish review. Feed discovery, creation, templates, scheduling, cards, polls, hashtags, sharing, video, stories, carousels, reactions, recommendations and analytics now use consistent natural Irish instead of words for life, individual votes or expelling people; literal English retry text; vocal noises for reactions; misleading comment and sharing labels; or unmuted audio described with disability-related language. A whole-catalogue test permits only four reviewed numeric, counter and distance formats to remain language-neutral and rejects the documented failure patterns.
On the accessible frontend, an idle sign-in no longer times you out early with a countdown that lied. The "your session is about to end" warning appears five minutes before the timeout, but its countdown only ever counted 60 seconds — so it signed you out four minutes early, while the on-screen message still claimed "60 seconds" remaining. A 30-minute session was ending at about 26 minutes. The countdown now runs the full five minutes, the warning and the timer always agree on the time left, the message reads naturally in the member's own language (for example "4 minutes and 30 seconds"), and the sign-out can no longer fire twice. Locked by a regression test.
Irish Feed cards and polls now distinguish reactions, comments, achievements and pobalbhreitheanna accurately. Removing a like no longer reads as disliking a post; muting uses neutral wording; earned badges, comment actions, sharing outcomes, content-detail links, event countdowns, volunteer-credit plurals and rating labels describe the displayed action; and poll loading, expiry, status and results consistently refer to a pobalbhreith rather than an individual vote.
Irish Feed templates, scheduling and media controls now read as complete member actions. Reusable post, listing, event and goal prompts use natural community wording; voice input identifies browser support directly; scheduling buttons use Irish action forms; accessibility labels identify the post content type; and upload failures, validation messages, video limits and file-count plurals use accurate post and media terminology.
Irish Feed composer assistance and formatting controls now give complete, natural instructions. AI generation and exhausted-credit messages identify what happened; restored and cleared drafts use action wording; image compression and reordering no longer read as isolated software commands; and bold, italic, underline, bulleted-list, link-preview and media-format controls use accurate publishing terminology with accessible file-size spacing.
Irish Feed creation forms now distinguish a marketplace listing from subscribing or registering. Post, poll, listing, event and goal creation use natural completion and failure messages; listing labels consistently refer to a
liostú; public-goal guidance says other members can support the goal itself; image and example text is cleaned up; and group-member counts use the appropriate Irish number mutations.The Irish Feed core now preserves discovery, moderation and realtime actions accurately. Listings and polls no longer use words for lists and votes; feed filters no longer call the feed “life”; draft recovery avoids literal composer terminology; and muting a user uses neutral wording rather than language associated with disability. The realtime new-post banner once again tells members to tap to view the posts across every Irish plural form, and the “like” failure now identifies the actual reaction rather than affection.
Irish member-shell controls now use the platform’s actual content and clearer recovery language. Global search now looks for listings and members rather than generic lists and committee members; quick actions, clearing filters, quick creation and error recovery use complete natural instructions; and identity badges distinguish a verified email address from an email message while stating unverified identity as an action that has not happened.
Irish wellbeing, caregiver-load and match-dismissal wording now speaks clearly from the member’s perspective. Low mood and fair wellbeing scores no longer read like object states or moral judgements; caregiver load and rest guidance identify the caregiver and avoid system-log language; and dismissal reasons now say “I am not interested”, “too far from me”, or that a match is outside the member’s skills instead of describing an unnamed third person.
Irish local-discovery and regional-points journeys no longer contain misleading literal translations. Club schedules now say when a club meets rather than that it “hits”; marketplace rates use a complete per-hour label; and the regional-points summary no longer describes lifetime earnings as floods or member IDs as wall identifiers. The malformed transfer amount example and missing negative transaction empty state are repaired, with the numeric format explicitly reviewed as a language-neutral invariant.
Irish skill discovery, personal-data export and problem reporting now preserve their full member-facing meaning. Skill search now searches for skills rather than categories and uses clearer member and proficiency wording; the data-export introduction restores the omitted explanation of machine-readable JSON and the right to move data to another service; and technical-support reports now distinguish blocked core tasks, diagnostic information and reference numbers accurately. Focused assertions guard the restored data-portability promise and repaired actions.
Irish coupon, time-credit redemption, saved-collection and thank-you journeys now describe the actions members actually take. Coupon QR expiry and in-store redemption, merchant discounts and usage limits now use natural retail wording; the member ledger identifies time-credit redemptions rather than abstract loyalty; saved-item empty states and visibility controls are grammatical; and public versus private thank-you notes clearly identify who can see them. Focused shared-catalogue assertions protect the repaired meanings.
The React donation journey now stays a donation journey in Irish. Support levels, recurring and one-off donations, checkout returns, payment recovery, cancellation and supporter recognition no longer describe a paid “premium” subscription or promise extra paid features that the English source explicitly says are not being sold. The shared-catalogue test now protects that distinction.
Irish support journeys in the shared React catalogue now use clear, people-centred wording. Asking for help by voice, gifting or transferring banked hours, viewing support relationships and future-care balances, recording an informal favour, and accepting an invitation no longer use gender-slashed recipient text, cheque terminology for a check-in, literal URL-slug or logging jargon, or misleading machine translations for reciprocity and care. Focused assertions protect the repaired wording while the wider
commoncatalogue remains under semantic review.The settings pages now say "we could not load this" instead of showing empty or default settings when the server is down. On the main settings page and the linked-accounts, appearance and availability pages, a backend hiccup used to make it look as though you had no linked accounts, no saved availability, or default preferences — which could mislead you into thinking your settings had been lost or reset. Each now shows a clear "could not load" notice at the top when a section fails to load, so you know it's a temporary problem rather than your real settings. Sign-in problems still send you to log in. Covered by tests for each page.
Deleting a marketplace listing or a podcast episode now asks you to confirm first. Both used to delete immediately on a single click, with no way back. They now take you to a short "Delete this listing? / Delete this episode? — this is permanent and cannot be undone" page with a clear "Yes, delete" button and a Cancel link, so a stray click can't wipe a listing or episode. The confirmation is a shared page reused by both, translated into every language, and its delete button is also guarded against a double-click.
If your session expires while opening a volunteering page, you're now sent to log in instead of a "service unavailable" or "couldn't load" page. On the volunteering list and a volunteering opportunity's detail page, an expired or revoked sign-in used to show a dead-end error page rather than the login screen every other page sends you to. Both now redirect to login, so you can sign back in and carry on. Covered by a test for each page.
Uploading a too-large file now shows a clear "the file is too large" message instead of a blank error page. On the accessible site's ~21 upload forms (photos, documents, resources, event and listing images, and so on), attaching a file over the size limit produced a generic "problem with the service" page. It now shows a proper "the file is too large — go back and choose a smaller file" page, translated into every language. (Re-filling the rest of the form automatically after an over-size upload is a separate, larger change and is not part of this fix.)
More money and delete buttons on the accessible site are now protected against an accidental double-click, and a member-to-member transfer can no longer skip its safeguards. Completing a group exchange, depositing into an organisation wallet, buying a shop item, making a marketplace offer, subscribing to premium, and deleting a marketplace listing or a podcast episode now all carry the same double-click guard the wallet transfer already had — so a quick double-tap can't fire the action twice. Separately, a member-to-member credit transfer now checks its confirmation box and its duplicate-protection key on the server, not only in the browser, so a transfer can't be sent unconfirmed or without a working safeguard against being counted twice. A ratchet test now lists every money/destructive button so a new one can't be added without this guard. (A full "are you sure?" confirmation step for the two delete buttons — beyond the double-click guard — remains a separate follow-up, since it needs new wording translated into every language.)
The Jobs pages now say "we could not load this" instead of pretending you have nothing when the server is down. On the main jobs list, your saved opportunities, your applications, and (for employers) your postings, a backend hiccup used to show a clean "you have none" — so your saved jobs or applications appeared to have vanished. They now show the same "could not load" notice used elsewhere on the site. Sign-in problems still send you to log in. This was the same fault fixed on the dashboard and Explore earlier; these four pages were missed because the same file already handled it correctly on its other pages. Covered by a test for each of the four pages.
Password-reset links and search terms are no longer written to the accessible site's server logs. The access log and the error log recorded the full web address of every request, including its query string — which meant a member's one-time password-reset token (carried in the reset link) and their search terms were being written to the logs. Anyone able to read those logs could in principle have lifted a still-valid reset token and taken over that account. Both log points now record the path only, without the query string — the same choice already made deliberately for crash reporting. Covered by a test that logs a reset-link URL and confirms the token never appears.
Shared React journeys have received another contextual Irish repair pass. Caring Community help and banked-care copy now describes neighbours, intergenerational support, companionship and reciprocity accurately; marketplace collection uses natural pickup terminology; member sorting, accessibility navigation, installation guidance, overdue fees and retry actions no longer use literal machine wording. The same pass repairs developer and analytics terminology for OAuth2, webhooks, scoped endpoints, footfall and privacy buckets, and preserves the machine-readable CSV import header and example instead of translating functional field names. The
commoncatalogue remains under review; this release note records the completed bounded slice rather than claiming the full namespace is finished.The React navigation and member-directory shell now uses accurate Irish labels and route descriptions. Donate, Ideas, Saved items, What’s on, Volunteering, Organisations and the WCAG 2.2 AA route now describe the destinations they actually open; the dashboard is no longer described as a personal house, and the accessible route no longer calls the retired interface alpha. Cookie controls, help centres, source attribution, problem reporting, member search, empty states, location filters, joining dates and exchanged-hour labels now use natural Irish. Project NEXUS remains unchanged as a product name and is explicitly reviewed as a language-neutral invariant. The wider
commoncatalogue remains under semantic review.The complete 847-value React Events catalogue has received a contextual Irish review. Event discovery, details, recurring-event setup, registration, waitlists, manual attendance, agendas, delegated teams, calendar feeds, reminders and public event pages now use consistent event and attendee terminology and natural action wording. The review repairs corrupted punctuation, roster/menu and assignment/task mistranslations, restores the private-feed security warning, preserves the RRULE protocol token, and replaces literal English constructions for recurrence, no-shows, check-in and immutable audit history. A whole-catalogue test permits only ten reviewed protocol, token, distance, product-name and RSVP values to remain language-neutral.
The entire 693-value React Public catalogue has completed its contextual Irish review. The changelog, Home, About, Contact, FAQ, maintenance, knowledge-base, community chooser and app-installation journeys now use natural Irish instead of literal wording for “one hour at a time”, opting out, saved apps, browser actions, negative balances, no-shows and guided help. The complete feature inventory now restores omitted provider and version details and correctly describes build profiles, paseochracha, service listings, marketplace payouts, saved vacancies, fochuntais, event rewards, social prescribing, trust levels, Copilot, cron tasks, performance queries and community provisioning. Whole-catalogue tests permit only 11 reviewed email, punctuation, operating-system, technology and CRM values to remain language-neutral and reject the documented detector-blind failure patterns.
The entire 404-value React About catalogue has completed its contextual Irish review. The timebanking guide, impact summary and report, partnerships, social prescribing pathway, and 2026–2030 strategic plan now use natural Irish instead of literal machine phrasing, malformed accents and grammar, incorrect public-health and SROI terminology, or book-chapter wording for community branches. The review also repairs a corrupted exchanged-hours statistic while preserving 13 names, brands, figures and monetary formats as reviewed language-neutral invariants; a whole-catalogue test rejects the documented failure patterns.
The entire 239-value React Authentication catalogue has completed its contextual Irish review. Registration, invitations, waitlists, account approval, sign-in, two-factor authentication, paseochracha, password recovery, email verification and identity verification now use consistent natural Irish instead of English organisation and surname examples, mixed password terminology, literal wording for an available waitlist place, malformed sign-in metadata, or verification sentences with unclear agency. A whole-catalogue test permits only six reviewed email, security-code and weak-password examples to remain language-neutral and rejects the documented failure patterns.
The entire 392-value React Listings catalogue has completed its contextual Irish review. Browsing, creation, editing, discovery filters, maps, listing details, saving, sharing, expiry, renewal, analytics, reciprocal matches and safety reports now use consistent listing terminology and natural Irish instead of English hour abbreviations, list wording for individual listings, malformed accents, emotional wording for experience, literal machine copy, or Ireland-specific location examples on a global platform. A whole-catalogue test permits only 11 reviewed distances, numeric examples, character counters and radius formats to remain language-neutral and rejects the documented failure patterns.
The member-facing React Caring Community journeys now use consistent, people-centred Irish. Trust levels, caregiver links, respite cover, provider contact actions, personalised onboarding, support relationships, personal-data export and the Warmth Pass no longer mix layers, series and trust levels; describe a care dashboard as chores; retain English words such as “Reach”, “Handoff”, “Search” or “Since”; run placeholders into surrounding words; or imply that the member rather than the community is the person placing trust. The catalogue's 14 English-identical values are now explicitly limited to formats, identifiers and established Swiss-domain terms. A focused member-journey test protects this slice while the administration half of the mixed Caring Community catalogue remains under review.
Irish shared React controls no longer fall back to English, and passkeys are no longer described as passwords. The 2,603-value
commoncatalogue now translates its remaining generic remove, confirm, delete, user, yes/no, theme and level controls, while its 50 language-neutral matches are explicitly limited to age bands, units, formats, symbols, examples, technical metrics and established product or Swiss-domain names. The same review repaired malformed navigation and loading terms, caregiver workload spacing, provider accessibility labels, an English CRM timestamp, and every device-specific passkey instruction. A focused gate prevents new exact-English shared fallbacks and rejects these detector-blind failure patterns; the broader semantic review of the rest of this large shared catalogue continues separately.The entire 419-value React Gamification catalogue has completed its contextual Irish review. Achievements, badges, challenges, collections, the XP shop, goals and goal buddies, community statistics, leaderboards, member journeys, featured members and NexusScore now use natural Irish instead of English fallbacks, missing accents, posts described as jobs, streaks described as streams, totals used for completed actions, check-ins described as inspectors, malformed day suffixes, or literal machine wording. A whole-catalogue test permits only 13 reviewed XP, unit, score-format and placeholder values to remain language-neutral and rejects the documented failure patterns.
The entire 811-value React Groups catalogue has completed its contextual Irish review. Discovery, membership, welcome templates, invitations, discussions, member administration, announcements, challenges, files, media, webhooks, Q&A, the group wiki, scheduled posts, analytics, exports and branding now use natural Irish instead of untranslated controls, accentless welcome copy, list-management wording for invitations, meeting terminology for joining, a pole for webhook deletion, malformed time suffixes, analysts for analytics, or Ireland-specific location examples. A whole-catalogue test permits only 13 reviewed formats, units, URLs and placeholder-only values to remain language-neutral and rejects the documented failure patterns.
The remaining React Volunteering feature sections have completed their contextual Irish review. Donations now describe current fundraising campaigns, card payments and offline bank-transfer pledges rather than obsolete giving-day behavior; safeguarding, accessibility accommodations, community projects, credentials, certificates, emergency requests, wellbeing guidance and the organisation wallet now use accurate, sensitive terminology instead of accessories, downloads for uploads, English burnout and shift fragments, or malformed automatic-credit instructions. Together with the core journey gate, focused tests now cover the complete 986-value source catalogue and its 15 reviewed language-neutral invariants.
The core React Volunteering journeys now use context-aware Irish for members and organisation managers. Opportunity discovery, applications, logged-hour approval and automatic credits, shift registration, waitlists, swaps, credentials, group reservations, receipts and check-in wording no longer contain missing accents, English controls, software-application terminology, “apply” phrased as putting something into effect, or shift and decline mistranslations. A focused catalogue test classifies the 15 remaining language-neutral formats, currencies, URLs, brands and placeholder-only values and rejects these known semantic failures.
The entire 946-value React Jobs catalogue has now received a contextual Irish review. Vacancy discovery, applications, employer tools, pipeline automation, analytics, interviews, hiring teams, moderation, talent search, AI assistance and onboarding now use natural job-specific language instead of malformed day suffixes, software-application terminology, inconsistent AI initials, missing accents, literal flag nouns, mistranslated hiring language or broken page metadata. A whole-catalogue test permits only 25 reviewed currencies, formats, brands, URLs and placeholders to remain language-neutral and rejects known corruption, whitespace and invisible characters.
The entire 230-value React Podcasts catalogue is now contextually reviewed for Irish. Podcast Studio, publishing readiness, media states, show lifecycle messages, episode metadata, visibility and moderation labels now use natural Irish instead of English fallbacks, verbal “display” mistranslations, malformed archive actions or incident terminology. A whole-catalogue test permits only the reviewed A–Z and playback-speed formats to remain language-neutral and rejects whitespace and invisible-character corruption.
The React podcast listening experience has received a contextual Irish repair. Browsing, show following, episode counts, transcripts, chapters, reactions, episode types and player errors now use natural Irish instead of English fallbacks or misleading words for display actions and incidents. A listener-catalogue test permits only the reviewed A–Z and playback-speed formats to remain language-neutral.
Seventeen more signed-in pages on the accessible site now have an automated accessibility check. The browser-based accessibility gate only covered the dashboard, account and a handful of other signed-in pages. A fast new check now also guards the marketplace, jobs, ideas, courses, resources, exchanges, goals, connections, matches, federation, premium, coupons, polls, blog, reviews, skills and volunteering pages — confirming each has exactly one main content region, a working "skip to content" target, a page heading, and no duplicated element identifiers (a common cause of confusion for screen readers). It runs in every language with no browser needed, so a future change that breaks one of these basics is caught immediately.
The native app's entire 46-string marketplace offer workflow is now reviewed for Irish. Offer creation, received and sent states, acceptance, rejection, withdrawal and counteroffers now use context-aware negotiation language instead of wording for opposition or a physical counter, and the malformed amount example was repaired. A whole-section test permits only the reviewed numeric amount format to remain language-neutral.
The native app's entire 50-string marketplace pickup workflow is now reviewed for Irish. Customer reservations, collection windows, QR-code instructions, seller slots, recurring schedules, scanning and pickup statuses now use natural context-aware Irish instead of mixed English fragments or misleading literal wording. A whole-section test permits only three reviewed date and capacity examples to remain language-neutral.
The rest of the accessible site's numbers now follow the reader's language too. The same US-only number formatting was fixed everywhere else it appeared: time-credit hours on group exchanges, volunteer hours and expense amounts, member and course star ratings, course costs, goal figures and coupon amounts. A German or French reader now sees "1,5" where an English reader sees "1.5", consistently across the site. English output is unchanged, and the shared locale-aware formatter used here is the same one proven by the German-price test in the previous change. (One low-traffic organiser-only analytics percentage on the event dashboard is left for a follow-up, as it needs proper percent formatting rather than a simple separator fix.)
Marketplace and premium prices now use each language's own number format. Prices, offers, order totals and subscription costs on the accessible site were always formatted the US way — a full stop for the decimal and a comma for thousands (for example "USD 1,234.50"). On a site serving eleven languages that is wrong for readers who expect the opposite (German and French write "1.234,50"). Prices now follow the reader's chosen language, and time-credit amounts and seller ratings do the same. English is unchanged. Proven by a test that renders a price in German and checks it uses a comma decimal.
Irish marketplace text no longer contains accidental leading spaces. Thirty-three labels, hints, placeholders and states across forms, offers, orders, coupons, seller tools, search, shipping and Stripe onboarding were normalized, with several adjacent malformed phrases repaired. A catalogue-wide whitespace test prevents the formatting defect from returning.
The native app's entire 32-string marketplace map workflow is now reviewed for Irish. Search-area guidance, coordinate states, radius and nearby-result labels now use context-aware Irish; broken markup, placeholder copy, a malformed latitude example and marketplace grammar were repaired. A whole-section test permits only five reviewed coordinate and distance formats to remain language-neutral.
Marketplace order, seller and promotion terminology in the native app has received a contextual Irish repair. Fourteen mistranslated or English labels now correctly describe parcel tracking, order ratings, delivery methods, seller messaging, average ratings and promotion placement. A focused test guards against the previously misleading words for “song,” “September,” and property listings returning.
The native app's entire 77-string Ideation catalogue is now reviewed for Irish. The final eighteen challenge filters, lifecycle states, idea outcomes and sorting labels now use context-aware Irish, and a stray space was removed from the date-time example. A whole-catalogue test verifies every key and permits only that reviewed date-time format to remain language-neutral.
Idea proposals and voting in the native app now stay in Irish. Thirteen proposal-form labels, submission outcomes, vote controls and empty states now use context-aware Irish. A focused test protects the complete member-contribution slice.
Ideation challenge discovery in the native app now stays in Irish. Fifteen challenge-search, category, count, disabled, empty and error strings now use context-aware Irish while preserving dynamic counts. A focused test protects the complete discovery slice.
The native app's entire 86-string Goals catalogue is now reviewed for Irish. The final fifteen support-companion, milestone, history, check-in, streak and reminder-frequency strings now use context-aware Irish while preserving names and dates. A whole-catalogue test verifies every key and permits only the reviewed percentage display to remain language-neutral.
Goal details, progress updates and reminders in the native app now stay in Irish. Nineteen detail labels, not-found and failure states, progress controls, save states and reminder settings now use context-aware Irish while preserving dates and numeric examples. A focused test protects the complete interaction slice.
Goal templates in the native app now stay in Irish. Twelve template-discovery, category, target, duration, selection and creation-error strings now use context-aware Irish while preserving titles, values and day counts. A focused test protects the complete template workflow.
The native app's entire 91-string Gamification catalogue is now reviewed for Irish. The final twenty-five XP-shop and profile-showcase labels, balances, purchase states, selection controls and errors now use context-aware Irish. A whole-catalogue test verifies every key and permits only seven reviewed numeric XP, score and progress displays to remain language-neutral.
Gamification challenges and learning journeys in the native app now stay in Irish. Thirteen challenge, journey, completion, reward-claim and error strings now use context-aware Irish while preserving raw XP and progress displays. A focused test protects both progression workflows.
Progress, Nexus Score and daily rewards in the native app now stay in Irish. Twenty-eight progress summaries, statistics, reputation signals, streak labels, reward actions and errors now use context-aware Irish while preserving XP, score-fraction and percentage displays. A focused test protects the complete progress-and-rewards slice.
The native app's entire 197-string Members catalogue is now reviewed for Irish. The final seventeen appreciation-wall labels, empty and error states, sign-in guidance and reaction controls now use context-aware Irish. A whole-catalogue test verifies every key and permits only four reviewed functional formats and identifiers to remain language-neutral.
Saved-item collections in the native app now stay in Irish. Thirty-two collection titles, controls, visibility labels, empty states, errors and item metadata strings now use context-aware Irish while preserving names, dates and counts. The generated type-and-ID fallback remains unchanged as a reviewed functional format, and a focused test protects the complete collection workflow.
Member discovery and public profiles in the native app now stay in Irish. Thirty-two listing, trust, review, achievement, appreciation and collection-navigation strings now use context-aware Irish while preserving names, counts and URLs. The profile share format, numeric transfer range and established
XPlabel remain unchanged as reviewed functional values, and a focused test protects the complete profile section.A new shared load-error message on the accessible site is translated into Irish. The reusable failure state now tells Irish readers that the content could not be loaded and to try again, instead of falling back to English.
The native app's entire 290-string Jobs catalogue is now reviewed for Irish. The final 34 owner-analytics labels, measures, comparisons, trends and prediction states now use context-aware Irish while preserving count, percentage, salary and market placeholders. A whole-catalogue test verifies every key and permits only twelve reviewed form examples, ranges and number-only formats to remain language-neutral.
Job applications and the employer hiring pipeline in the native app now stay in Irish. Eighteen stage, candidate, application-message, history, withdrawal and error strings now use context-aware Irish while preserving dates and stage-name placeholders. A focused test protects both sides of the workflow.
Job alerts in the native app now stay in Irish. Thirty-four tab, search, filter, status, date, success and error strings now use context-aware Irish, including time-commitment and remote-role wording. A focused test protects the complete alerts journey.
The native app's entire 237-string Settings catalogue is now reviewed for Irish. The final 38 identity-verification labels, payment instructions, document requirements, progress states and errors now use context-aware Irish while preserving Stripe and fee placeholders. A whole-catalogue test verifies every key and permits only the reviewed date format,
JSONidentifier and example email address to remain language-neutral.Linked-account settings in the native app now stay in Irish without blurring consent boundaries. Thirty-four request, approval, delegated-access, status and permission labels now use context-aware Irish and describe member-approved account capabilities rather than staff-recorded guardian arrangements. The email example remains unchanged as a functional value, and a focused test protects the workflow.
Blocked-user controls and personal-data exports in the native app now stay in Irish. Thirty-nine privacy labels, explanations, confirmations, warnings, formats, progress states and errors now use context-aware Irish. The
JSONformat name remains unchanged as a technical identifier, and a focused test protects both sensitive workflows.Language and content preferences in the native app now stay in Irish. Thirty-two account labels, feed-ordering controls, automatic-translation settings, save states and language names now use context-aware Irish. A focused test protects the complete preferences slice.
The native app's entire 303-string Volunteering catalogue is now reviewed for Irish. The final 25 shift-swap labels, requests, actions, errors and statuses now use context-aware Irish. A whole-catalogue test verifies every key and permits only seven reviewed functional formats for dates, times, currency and percentages to remain language-neutral.
Volunteer organisation wallet and settings controls in the native app now stay in Irish. Twenty-one balance, automatic-payment, deposit, transaction and organisation-editing labels and errors now use context-aware Irish. A focused test protects both management panels.
The native app's volunteer organisation dashboard no longer falls back to English in Irish. Forty-seven dashboard labels, states, tabs, statistics, roles, application actions, hour-approval controls and volunteer summaries now use context-aware Irish while preserving names, dates, hour totals and application counts. A focused test protects the complete organisation-operations slice.
The accessible dashboard, Explore and search pages now say "we could not load this" instead of pretending you have nothing. When the backend had a hiccup, these pages quietly showed empty cards or "no results" — so a member couldn't tell an outage apart from genuinely having no listings, events or matches. Now, if part of a page fails to load, a clear notice appears at the top; the search page shows the same notice rather than a misleading "No results found". Sign-in problems still send you to the login page as before. The notice is translated into all ten languages (Irish shows the English wording for now). Covered by tests that fail each page's data load and check the notice appears — and that a fully working page shows none.
An empty chat now has a proper heading, and a one-to-one thread with no messages says the right thing. When you open a conversation that has no messages yet, the page showed only a sentence with no heading, so a screen-reader user moving heading by heading skipped past it. It now shows a "No messages yet" heading (translated into every language) above the message. Separately, a one-to-one thread with no messages was showing the wrong sentence — "There are no conversations to show", which belongs on the inbox list — and now correctly says "No messages in this conversation yet." A contract test keeps the heading on both the direct and group conversation pages.
A member's profile page now says "we could not load this" if one of its sections fails to load. A profile is built from several parts — badges, reviews, skills, listings, recent activity, endorsements — each fetched separately. If one had a backend hiccup it used to come back empty, so a member with plenty of reviews or skills could look as though they had none. The core profile still shows as before, and now a single notice appears at the top if any section could not be loaded. The deliberate safety behaviour is unchanged: if we cannot confirm whether someone has blocked you, we still assume they might have, and sign-in problems still send you to login. Covered by a test that fails a section and checks the notice appears — and that a fully loaded profile shows none.
The accessible activity, leaderboard, achievements and nexus-score pages now say "we could not load this" too. These pages had the same problem — a backend hiccup showed an empty timeline, an empty leaderboard, no badges or a zeroed score, none of which a member could tell apart from genuinely having none. All fourteen of their views now show the shared notice at the top on a load failure, while any single card that fails still leaves the rest of the page working. Sign-in problems still redirect to login. Covered by a failure-path test for each of the four sections.
The accessible wallet donate form no longer throws away what you typed when a donation is refused. Like the transfer form before it, a rejected donation to the community fund — too large, not enough credit, and so on — reloaded with the error but wiped the amount and message you'd entered. Now both are filled straight back in, ready to correct and resend. They're kept server-side for the reload rather than put in the web address, since they're financial input. Covered by a test that fails a donation and checks the amount and message come back.
The accessible wallet transfer form no longer throws away what you typed when a transfer is refused. If a transfer failed — too large, not enough credit, and so on — the page reloaded with the error but the recipient, amount and note you'd entered were all gone, so you had to search for the person and type everything again. Now the recipient reappears and your amount and note are filled back in, ready to correct and resend. The amount and note are kept server-side for the reload (never put in the web address, since they're financial), and a fresh anti-double-submit key is issued on the retry so there's no risk of sending twice. Covered by a test that fails a transfer and checks the values come back on the right recipient.
Volunteer expense claims and donations in the native app no longer fall back to English in Irish. Forty-one financial labels, prompts, validation messages, categories and statuses now use context-aware Irish. The
EURcurrency code and percentage format remain unchanged as functional values, and a focused test protects both workflows.Volunteer shifts and certificates in the native app now stay in Irish. Twenty-nine labels, actions, empty states and errors now use context-aware Irish, including approved-hour certificates and accessible action labels. Three language-neutral date and time formats remain unchanged, and the existing software-style translation of “My applications” is corrected to member-facing Irish. A focused test protects the reviewed slice.
The native app's entire 300-string Profile catalogue is now reviewed for Irish. The final 31 English descriptions for balances, messages, recommendations, marketplace tasks, jobs, events, ideas, volunteering and federation now use context-aware Irish. A whole-catalogue test verifies every key and rejects any exact-English fallback.
Profile navigation and photo updates in the native app now stay in Irish. Forty-one section labels, marketplace shortcuts, account headings, permission prompts and upload states now use context-aware Irish, including the distinction between a member's profile image and a generic avatar. A focused test protects this navigation slice.
Matches and reviews in the native app no longer switch to English for Irish users. Forty-seven labels, filters, actions, empty states and errors now use context-aware Irish for opportunity recommendations and member feedback, while score, rating and exchange-title placeholders remain intact. A focused test protects both Profile journeys.
The remaining Irish support summaries in the native app now cover cookies, accessibility, and trust and safety. Twenty-four headings and paragraphs explain device storage, canonical web cookie rules, accessible native and HTML-first experiences, reporting, member protection and the limits of platform tools in context-aware Irish. The legal-summary test now protects all five policy sections.
The native app's Terms and Privacy summaries no longer fall back to English in Irish. Sixteen legal-summary headings and paragraphs now use context-aware Irish while continuing to identify the full web terms and privacy policy as canonical. A focused test protects both summaries.
The native app's Irish support hub, About summary and Contact guidance no longer display English. Forty navigation labels, policy descriptions and member-support paragraphs now use context-aware Irish, including urgent-harm guidance and the AGPL source-availability summary. This pass also catches a stale English description that differed from the current source and therefore escaped exact-match counting; a focused test protects the reviewed legal-support slice.
The native app's entire 325-string Groups catalogue is now reviewed for Irish. The final 40 English fallbacks in group analytics now cover membership, engagement, retention, comparisons and activity breakdowns in context-aware Irish. A whole-catalog test verifies every key and permits only the two deliberately unchanged latitude and longitude examples.
Group tasks and marketplace tools in the native app now stay in Irish. The task board's 39 labels, statuses, priorities, due dates and errors, plus the group marketplace, member roles and detail statistics, now use context-aware Irish with dates, titles and counts preserved. A focused test protects all four reviewed sections.
Group questions, answers and wiki pages in the native app are now fully Irish. All 59 labels, prompts, voting actions, answer states, wiki editing controls, version-history messages and errors now use context-aware Irish with count and title placeholders intact. A focused test prevents either knowledge-sharing section from reverting to English.
Group files and media in the native app no longer switch to English for Irish users. All 32 labels, empty states, permission prompts, upload/download actions and deletion confirmations now use context-aware Irish while preserving filenames such as
{{name}}. A focused test protects both detail sections.Core group pages in the native app now stay in Irish. The group overview, discussions, announcements, upcoming events, owner tools and twelve detail tabs no longer display their 57 English fallbacks. Counts and interpolation placeholders are preserved, and a focused test protects this member-facing slice.
Creating and editing groups in the native app no longer falls back to English in Irish. The 43 user-facing fields, hints, validation messages, image errors and save states in the group form now use context-aware Irish, together with the Groups landing-page description, statistics and post count. Coordinate examples remain unchanged as functional numeric values, and a focused test protects the reviewed slice.
The native app's Irish Explore screen is now fully translated. All 64 discovery labels and descriptions now use context-aware Irish, including recommendations, popular and nearby listings, community statistics, upcoming events, groups, people, volunteering, organisations, jobs, polls and resources. A focused catalog test prevents the whole screen from silently reverting to English.
Irish event-safety wording is clearer and no longer contains English status labels. The native app now translates the six remaining English guardian and acknowledgement statuses, and a semantic review also replaces machine-like safeguarding-policy and member-block phrases, improves guardian relationship wording, and removes invisible zero-width characters from participation-block messages. Matching wording in the PHP event-safety catalogue is aligned where the same concepts are shown.
Irish mobile account recovery, verification and registration no longer fall back to English. The 45 remaining English values in the native app's authentication catalogue now use context-aware Irish, covering password reset, email verification, registration confirmation, password requirements, required-field errors and the platform terms acknowledgement. Functional examples such as international phone-number and email formats remain unchanged, and a focused test now prevents this catalogue from silently reverting to English. Independent native-speaker review is still recommended.
The measured Irish fallbacks in the accessible and PHP translation catalogs are cleared. The final 49 verbatim-English values covered counts and plurals across activity, comments, reactions, feeds, polls, goals, groups, jobs, messages, organisations, resources, saved items, search and volunteering, plus three "Post" labels, three shift labels, two reaction labels, a notification content type and a PDF not-applicable value. They now use context-aware Irish while preserving Laravel choice syntax and placeholders. The shrink-only untranslated audit now reports zero Irish values identical to English unless they are intentionally allowlisted; independent native-speaker review is still recommended and React/mobile semantic review remains separate work.
Development-only ASP.NET backend: whole features were switched off without anyone being told. None of this affects the live platform — the ASP.NET backend is a development-only second implementation and is not deployed. The information a page fetches when it first loads was missing 20 of the platform's on/off switches — marketplace, courses, podcasts, identity verification, two-step sign-in and fingerprint sign-in among them. The app treats a missing switch as "this feature is off", so on this backend those features simply did not appear, with nothing anywhere to say why. Also missing were the community's currency (so every price and time-credit figure had nothing to format with) and the list of available languages (so a member had no way to change language at all). All now present, using the main platform's own default settings rather than any one community's choices — copying a single community's settings would have quietly made its preferences everyone else's defaults.
Development-only ASP.NET backend: we can now check the pages you only see once you are signed in — and they were much further out than they looked. None of this is deployed. Until today the two backends could only be compared while signed out, because the only local copy of the live database holds real member data and must not be logged into. Building a throwaway copy — the shared database structure plus a handful of invented test accounts, no real data at all — made the rest visible. Signed out, 164 of 170 pages matched. Signed in, it was 17. That is not a step backwards; it is the first honest look behind the login. It is now 57, with another 31 correct in structure but not yet fully checkable because the test community only has three people and one listing to compare.
- Four things were being handed to signed-in members that the live platform refuses. Talent search listed every member who had agreed to be seen by employers to anyone at all. Community federation — which shares a member's profile beyond their own community — was serving people who had never opted in, so the opt-in was doing nothing. Marketplace promotions ignored the separate switch that controls them. Paid push campaigns, which send notifications to a community and charge for them, had no check whatsoever.
- Two pages accepted nonsense. One answered "yes, you can go ahead with this exchange" when the request did not name a listing at all. The knowledge-base search returned every article when the search box was empty.
- Sixteen list pages reported page numbering in a form the app cannot read. They sent a page count and a row total; the live backend sends how many fit on a page and whether another page exists. Anything built to show "next page" from the live backend's answer would have found nothing here. Two shared pieces of code were responsible for most of them.
- Nearly every page was missing a small block of information the live backend attaches to its answers, and 41 carried an extra flag it never sends. Both are now handled in one shared place, mirroring how the live backend does it. That change corrected 130 pages at once and required updating 122 tests that had been quietly recording the wrong behaviour as correct.
Development-only ASP.NET backend: every page the React app loads now answers exactly like the live one. None of this is deployed — the ASP.NET backend is a development-only second implementation of the same platform, built so the app could one day run on either. Measured against the live backend across the 170 addresses the app calls, it now gives the same answer on 164 and differs on none; the other six could not be compared because one side had no example data to compare. It was 136 at the start of the day.
- Web addresses ending
/v2/were letting people in without a login. The backend publishes many pages at two addresses. The second address was being built in a way that quietly dropped the "you must be signed in" rule, so eleven pages ran for anyone who asked. Ten showed nothing only because the community that owns them had that section switched off; one crashed instead. This was invisible to every existing check, because both addresses exist and a list of addresses cannot tell you one of them forgot to ask who you are. - A community's feature switches were invisible to half the platform. Switches are stored under a name, and two different spellings of that name had grown up. The part that decides whether to serve a feature read one; the part that tells the app which features exist read the other. So a community could switch its public events page ON, have the backend serve it, and have the app still hide it — or switch a feature OFF and have the app keep advertising a page the backend refuses. All forty feature switches were affected. Both spellings are now read, so no community's existing setting is lost.
- The sign-up page was told the wrong thing about whether sign-ups were open. Three answers were fixed values rather than being worked out from the community's actual setting, so a community that had closed registration was still told it could register.
- Smaller fixes to match the live backend: the community picker, the skills categories list (which was answering from the wrong list entirely), the categories list, public events paging, and seven pages that were missing a small block of information the live backend includes.
- Web addresses ending
Development-only ASP.NET backend: the last nine pages that answered strangers are now closed. None of this affects the live platform — the ASP.NET backend is a development-only second implementation and is not deployed. Course categories, marketplace categories, featured listings and promotions, jobs salary benchmarks and talent search, community stories, and the live-updates connection settings all returned data to callers with no login, where the main platform requires one. They returned little only because the demo data is thin; with real data they would have exposed it. A ninth, the paid membership tiers, was different: the main platform hides it when a community has not switched that feature on, while this backend advertised paid tiers to everyone regardless. All nine now behave exactly as the main platform does — including refusing in the same way, since "not available here" and "please sign in" are different answers and the app treats them differently.
Development-only ASP.NET backend: a fourth "please sign in" shape, and eight more screens fixed. None of this affects the live platform — the ASP.NET backend is a development-only second implementation and is not deployed. After unifying three of them, measuring the main app's own list of requests found a fourth: when the "not signed in" reply came from the error handler rather than the page itself, it used a completely different format again, and in development it attached a stack trace to what is not an error at all. Eight screens were affected — saved job profiles, interviews, offers, blocked members and linked accounts among them. All now return exactly what the main platform returns.
Development-only ASP.NET backend: "please sign in" came back in three different shapes. None of this affects the live platform — the ASP.NET backend is a development-only second implementation and is not deployed. Depending on how each page was protected, a request without a login got one of three different replies: the correct one, a raw technical error page, or a third variation. The app reads that reply to decide what to do next, so it could not treat them as the same thing — meaning a member could be signed out on one screen and see a confusing failure on another. All of them now return exactly what the main platform returns, fixed in one place so it cannot drift apart again.
Development-only ASP.NET backend: three pages were locked that should be open, and a jobs list was open that should be locked. None of this affects the live platform — the ASP.NET backend is a development-only second implementation and is not deployed. Clubs, popular listing tags and skill categories are all public on the main platform, but this backend demanded a login — so a signed-out visitor browsing a community saw nothing on three pages that work elsewhere. Two of those are marked as deliberately public in the main platform's own routing, so there was no ambiguity about the intent. Going the other way, the jobs list was readable without a login here while the main platform requires one; that is now closed too, though the reason it slipped through is not yet understood and has been written down rather than glossed over.
Development-only ASP.NET backend: members' names and email addresses were readable by anyone. None of this affects the live platform — the ASP.NET backend is a development-only second implementation and is not deployed. Checking the accessible site's requests against both backends for the first time found that the member search returned names and email addresses to a caller with no login at all, where the main platform requires one. Five more pages behaved the same way: courses, podcasts, jobs, and the two resource-category lists. All six now require a login, matching the main platform. This was only findable by asking both backends the same question — the accessible site had never been checked against this backend before, and the earlier check covered the main app only.
Development-only ASP.NET backend: it was giving away things the main platform keeps behind a login. None of this affects the live platform — the ASP.NET backend is a development-only second implementation and is not deployed. A new side-by-side comparison tool asks both backends the same question and compares the answers, and the first run found that the resources list handed real community content — titles, descriptions, files — to someone with no login at all, while the main platform requires one. Marketplace listings and volunteering organisations did the same, and looked harmless only because the demo data is empty. One case ran the other way: categories are public on the main platform but demanded a login here, so a signed-out visitor lost a page that should work. All four now match. This mattered because no previous check could see it — every existing tool compared lists of web addresses, and both backends "have" these addresses; only asking them both the same question revealed that one answers to strangers.
Development-only ASP.NET backend: backing a community project was entirely cosmetic. None of this affects the live platform — the ASP.NET backend is a development-only second implementation and is not deployed. Tapping "support" nudged a number the page had made up, saved the fact into an unlabelled settings entry, and the list of projects did not carry a supporter count at all — so the number reset the moment the page reloaded, and nobody organising a project could see who had backed it. Support is now recorded properly: the count is real and shared, whether you have backed it is yours alone, backing twice still counts once, and withdrawing support takes the count back down.
Development-only ASP.NET backend: archiving a conversation did nothing, and a wellbeing check-in never reached the people meant to follow it up. None of this affects the live platform — the ASP.NET backend is a development-only second implementation and is not deployed. Archiving a conversation saved a note into an unlabelled settings entry, while the inbox works out its "archived" tab from the messages themselves — so the conversation reported as archived and stayed exactly where it was, every time. It now moves properly, and archiving is personal: filing your own copy away does not hide the conversation from the other person. Separately, a volunteer's wellbeing check-in was being saved into another unlabelled entry, even though a fully working version already existed at a slightly different address — so a volunteer could say they were struggling, be thanked for checking in, and the follow-up list that exists to notice exactly that would never see it. Check-ins now reach it, and a very low mood is flagged for follow-up automatically rather than depending on someone reading every entry.
Development-only ASP.NET backend: an expense claim reached neither the volunteer nor a reviewer. None of this affects the live platform — the ASP.NET backend is a development-only second implementation and is not deployed. Submitting an expense claim saved it into an unlabelled settings entry, while the list of claims on the very same screen read the proper records store. So a volunteer submitted a claim with a receipt, was told it was submitted, and it appeared nowhere. It could not have worked in any case: the submit step expected a different kind of request from the one the screen sends, so the claim never even arrived. Claims now go to the proper store, arrive as awaiting review rather than approved, keep their receipt, and are refused if they have no amount or no description — a reviewer cannot approve a claim that does not say what it is for. A claim without a receipt is still accepted, because refusing it outright pushes people to submit nothing at all.
Development-only ASP.NET backend: a member's donation never reached the people running the community. None of this affects the live platform — the ASP.NET backend is a development-only second implementation and is not deployed. Recording a donation saved it into an unlabelled settings entry; the member's own list of donations stayed empty; and the staff donations screen read a completely different place. Three ends, no two of them connected. Donations now go to the same place staff already look, so a member sees their own and staff see them too, with the amount stored exactly and impossible amounts refused. Importantly, recording a donation is not the same as taking a payment: every donation is saved as awaiting payment, nothing here contacts a card provider, and it is the existing staff "mark complete" step or the payment provider's own confirmation that moves it on. Marking money as received when it has not been is the money version of the same false-success problem being cleared elsewhere.
Development-only ASP.NET backend: a reported safeguarding concern reached nobody. None of this affects the live platform — the ASP.NET backend is a development-only second implementation and is not deployed. This is the most serious problem found in it. A volunteer could report a safeguarding concern — a disclosure, an allegation, a worry about a child or an at-risk adult — and be told "Incident recorded". Nothing recorded it anywhere anyone could read: the report went into an unlabelled settings entry, the volunteer's own list of reports stayed empty, opening any report showed a made-up "open" case that did not exist, and no staff screen read reports at all. So the concern was accepted, acknowledged, and lost, and neither the person who raised it nor a safeguarding lead had any way to notice. Reports are now stored properly and reach a staff queue that lists the most serious first. Nobody can delete a report — not even the person who made it — because a raised concern is a record; withdrawing it is a decision a safeguarding lead records with a reason. A volunteer sees only their own reports. Only staff can change a report's status, and closing one now requires a reason, since "why was this closed?" is the first question any review asks. Telling the designated safeguarding lead, and notifying an outside authority, are both recorded with the time they happened.
Development-only ASP.NET backend: volunteers' certificates, accessibility needs and reviews had nowhere to be stored. None of this affects the live platform — the ASP.NET backend is a development-only second implementation and is not deployed. Three volunteering screens had no database tables behind them at all, so each showed an empty list that looked exactly like "you have none". The certificates one was the worst: a volunteer could upload their first-aid certificate, be told it worked, and never see it again — and the download button handed back a made-up text file containing the words "Credential 5" instead of the real document. Certificates now work properly: upload, list and delete, with the deletion limited to your own so nobody can remove someone else's safeguarding evidence. Police-check and vetting documents are now refused by the server as well as the browser, because those must never be stored here. And a certificate that has passed its expiry date now shows as expired when read, rather than still saying "verified" — which matters, because "verified" is what a coordinator relies on when deciding who may work with children. The accessibility and reviews screens now work too. Accessibility needs were the strangest case: saving them did store something, but in a place no screen could ever read, so a volunteer recorded the support they need in order to take part, was told it was saved, and it vanished. Those needs are now stored properly, come back when the volunteer returns, can be withdrawn as well as added, and are visible only to the volunteer themselves. Organisation reviews now appear on the organisation's page instead of every organisation looking as though nobody had ever reviewed it; a second review from the same person updates their first rather than stacking, and impossible star ratings are refused both by the site and by the database itself.
Development-only ASP.NET backend: "sign out this device" and "sign out everywhere" did nothing. None of this affects the live platform — the ASP.NET backend is a development-only second implementation and is not deployed. Both replied that the sessions had been ended while leaving every one of them fully working. "Sign out everywhere" is what someone uses when they believe another person has got into their account, so answering "done" while the intruder stays signed in is the worst possible response to that question. Both now genuinely end the sessions, are recorded so it stays answerable who signed out and when, and stop at your own account — one member cannot sign another out, and a request that does not say which session to end is refused rather than guessed at.
Development-only ASP.NET backend: the super-admin community controls did nothing at all — including one that claimed to delete a community. None of this affects the live platform — the ASP.NET backend is a development-only second implementation and is not deployed. All six buttons for managing communities (create, edit, deactivate, reactivate, allow sub-communities, move) replied "done" and changed nothing. The worst was the delete button: it answered "Tenant deleted" while the community stayed fully live, which a platform administrator could reasonably have believed. All six now work properly, with the safeguards the main platform has: the master community can never be deleted or moved; deleting is a deactivation, not a wipe, and is refused while a community still has active sub-communities; a community cannot be moved underneath one of its own sub-communities; moving a community correctly moves everything beneath it; a community name that would clash with a platform page (like "admin" or "api") is refused because it would be unreachable; and turning off a community's ability to have sub-communities also removes the network-administrator status that only existed because it had them. Testing this against a running copy also revealed that newly set-up demo communities were being created without their position in the community tree recorded, so nothing could be moved to them — now fixed. The two screens that show communities were fixed in the same pass: the list was a hardcoded empty list, and the detail page returned a blank record with a made-up creation date for whatever community you opened. The empty list mattered more than it sounds, because four moderation screens (feed, comments, reviews and reports) use it to fill their "filter by community" menu — an empty menu reads as "there are no other communities" rather than "this did not load". Opening a community that does not exist now says so instead of inventing one.
Development-only ASP.NET backend: two overdue safety routines that were never running now run. None of this affects the live platform — the ASP.NET backend is a development-only second implementation and is not deployed. First, copies of messages that a safeguarding reviewer had looked at were being kept for ever. They are now deleted once their retention period passes — 90 days for a message reviewed and cleared, a year for one that was flagged, since a flagged message may be needed as evidence. A message nobody has reviewed yet is never deleted, at any age, because that would quietly throw away a safeguarding concern no one has read. Second, when a member reported a marketplace listing and no moderator responded within 24 hours, nothing happened at all: the report sat untouched, the member was never told, and the seller never learned their listing had been reported. Responding within a day is a legal requirement, so the platform now acknowledges the report itself and tells both people — the member that their report is with the moderation team, and the seller that one of their listings is being reviewed and stays available unless a problem is found.
Development-only ASP.NET backend: group messaging can now exist at all, and a privacy feature had been quietly renaming groups. None of this affects the live platform — the ASP.NET backend is a development-only second implementation and is not deployed. That backend stored a conversation as exactly two people, so a group conversation could not be represented, while the app has a working "create a group" screen: a member could fill in the form, choose people, and get an error. Adding someone, removing someone and renaming a group all replied "done" and changed nothing. Groups are now real — any number of members, each either an organiser or a member, able to join and leave — and every existing one-to-one conversation was carried over so nobody loses access to their own message history. Only organisers can add, remove or rename; someone outside a group cannot read it or post to it; leaving keeps a record of who could see what and when. Separately, this uncovered that the feature which hides members' surnames was treating a group as if it were a person, and cutting the group's name at the first space — so "Alpha Bravo Charlie" appeared in the list as "Alpha". Groups are now exempt, surnames are still hidden, and that surname feature has its first tests after having had none.
Development-only ASP.NET backend: an audit found endpoints that reported success while doing nothing, and several member-facing faults were fixed. None of this affects the live platform — the ASP.NET backend is a development-only second implementation and is not deployed. An adversarial re-audit of it found, and this work fixed: members being logged out every time their sign-in token expired (the app's "keep me signed in" call reached an endpoint that had been retired, and the app reads that as "your login is bad"); two browser tabs refreshing at once logging a member out everywhere, rather than being recognised as a harmless race; a terms-and-conditions dead end where a member was blocked and the only two endpoints that could unblock them were not registered at the address the app calls; and a fake endpoint that reported a member had accepted the terms when no such record existed. Three webhook handlers — card payments, identity checks, and email bounce reports — replied "received" and threw the data away, which tells the sender never to retry; they now refuse honestly so the event stays in the sender's queue. On the carer/supporter feature: the emailed "confirm this action" link, which needs no login and authorises a time-credit transfer, had no rate limit and was brute-forceable; and a permission check answered "yes" to any unrecognised permission name, including for someone granted nothing. On guardian consent for children attending events: a consent link worked for every community rather than only the one that issued it; consent expired 24 hours after it was requested regardless of when the event was, so approval lapsed and the child was blocked; and a child could approve their own consent.
Added
A blocking check that counts backend endpoints which report success while doing no work, and only lets that number shrink. The audit's central finding was that 351 endpoints in the development-only ASP.NET backend answer with convincing "success" responses while performing no work at all. That class of fault is invisible to route comparisons and to the existing tests, which only check that a web address exists and responds — which a do-nothing endpoint satisfies. The new check reads what each endpoint actually does, and fails the build if the count rises (or if it falls without the recorded figure being updated in the same change). It was verified by deliberately adding a do-nothing endpoint and confirming the check caught it. Full findings and the remediation order are recorded in
aspnet-backend/docs/PRODUCTION_READINESS_REMEDIATION.md.Event agenda session times now use the accessible GOV.UK date-and-time fields, completing the changeover. Every session's start and end time on an event's agenda is now the date boxes plus a "Time" box. With this, every date-and-time picker on the accessible site — wallet slots, podcasts, event messages, registration campaigns and settings, event create/edit, recurring edits, and now the agenda — has moved off the browser's built-in widget to the GOV.UK Design System pattern the standards recommend. Two inputs are deliberately left as-is and recorded: a poll's closing date (its browser minimum is the only guard against a poll closing in the past) and the availability slots' plain time boxes (GOV.UK has no component there). Saved values are unchanged throughout; all covered by tests, with the main event-create form additionally checked end-to-end against a live backend.
Editing a repeating event's times now uses the accessible GOV.UK date-and-time fields (eighth conversion). Same saved value, same timezone handling; covered by tests.
Creating and editing an event now use the accessible GOV.UK date-and-time fields (the two most-used organiser forms, sixth and seventh conversions). The event's start and end are now three date boxes plus a "Time" box instead of the browser's date-and-time widget. What's saved is unchanged — the accessible frontend sends the same value it always did and the server does the timezone work — so existing events and their times are unaffected. Verified two ways: automated tests confirm the four fields recombine into exactly the value the event API receives, and a real event-create was run against the disposable test copy to confirm the round-trip works against a live backend. Seven of the site's seventeen date-and-time inputs (ten of eleven counting each occurrence) are now on the accessible pattern.
Event registration settings (the opening, closing and cancellation-cutoff times) now use the accessible GOV.UK date-and-time fields (fifth form converted). These times are stored in the event's own timezone; the accessible frontend passes the same value it always did and the server does the timezone work, so nothing about stored times changes. A part-filled time now shows a clear error instead of being silently cleared.
Event registration-invite campaigns now use the accessible GOV.UK date-and-time fields (fourth form converted): both the "invites expire at" time when sending now and the "send at" time when scheduling a campaign. Same saved values as before; covered by tests. Six of the site's seventeen date-and-time inputs are now on the accessible pattern.
Scheduling an event message now uses the accessible GOV.UK date-and-time fields (third form converted). When an organiser schedules a broadcast to attendees, the "send at" time is now the date boxes plus a "Time" box. A bonus safety improvement: if someone half-types a time, they now get a clear "there is a problem" instead of the message quietly being treated as "send now". Blank still means send now, exactly as before; the saved time is unchanged.
Podcast episode scheduling now uses the accessible GOV.UK date-and-time fields too (second form converted). Setting when an episode publishes is now the three date boxes plus a "Time" box, on both the "add episode" and "edit episode" forms, instead of the browser's date-and-time widget. The shared component gained the ability to appear more than once on a page without clashing, which these two forms need. Same saved value as before; covered by tests.
The marketplace pickup-slot times now use the accessible GOV.UK date-and-time fields (first form converted off the browser's built-in date-and-time picker). Setting a slot's start and end is now three plain number boxes for the date plus one "Time" box that accepts what people naturally type ("9:30am", "17:00"), instead of the browser widget the GOV.UK Design System advises against. What gets saved is exactly the same as before, so existing slots are unaffected; the change is verified by tests covering both the form and the save. The remaining date-and-time forms (events and other organiser screens) follow the same way, one at a time.
Groundwork for replacing the browser's date-and-time pickers with the GOV.UK pattern. The accessible site's remaining date-and-time fields (on event and organiser forms) still use the browser's built-in picker, which the GOV.UK Design System advises against. This change adds the reusable building blocks to convert them — a shared "date plus a single time field" form component and the server-side code that turns those fields back into the exact value the system already stores, plus the "Time" label, hint and error wording translated into all eleven languages. Nothing member-facing changes yet: the pieces are in place and fully unit-tested (time parsing accepts "9:30am", "14:30", "9.30" and rejects impossible times), and the forms will be converted one at a time in follow-up changes so each can be tested end to end.
The connections pages now say "we couldn't load this" instead of pretending you have no connections. When the connections service had a problem, both the connections page and the network page silently showed empty lists — a member would reasonably read that as "I have no connections and no pending requests," when in fact the data just failed to load. Both pages now show a clear "We could not load your connections. Please try again." notice (translated into all eleven languages) when the load fails, and still show the real empty state only when you genuinely have none. (A wiring bug meant the network page's notice would never have appeared — caught and fixed, with tests covering both pages.)
Empty pages on the accessible site now have a heading, so screen-reader users get a landmark. Across ~54 places — the leaderboards, saved items, search results, resources, courses, clubs, goals, jobs lists, marketplace and its orders, groups, ideation, federation browse pages, volunteering alerts, and more — a page (or a whole region of a page) that had nothing to show displayed only a sentence with no heading above it. A screen-reader user moving through the page heading by heading would skip straight past it. Each now shows a "No results found" heading (already translated in all eleven languages, so no new wording was needed) directly before the empty message. This was done with care: pages whose empty message already sits under its own section heading were deliberately left alone to avoid duplicate headings, and empty chat threads were left for a future pass because "No results found" is the wrong wording there. A contract test now keeps a heading on each of these empty states.
The marketplace category page's "Back to marketplace" link is now translated. It had one last hardcoded English fallback label; it now uses the existing translated wording, so it reads in the member's language like every other back link.
Two date/number display bugs on the accessible site. A knowledge-base article showed its "Last updated" date as a raw machine date (
2026-07-06) instead of a readable, translated date ("6 Jul 2026", and the equivalent in each language) — it now uses the site's standard date formatter. And marketplace coupon discounts were always formatted in US number style regardless of the reader's language; they now format numbers in the reader's own locale.The accessible site now prints cleanly. It had no print styling at all, so printing a page (a wallet statement, an event ticket, a volunteering record) carried the full site chrome — header, navigation, footer, cookie banner, language switcher and buttons — and wide tables were clipped to one screen width. A print stylesheet now strips that chrome, prints in black on white, shows the destination of content links, and expands scrollable tables so the whole table prints. It lives entirely inside the browser's print mode, so nothing about the on-screen site changes. (Not yet checked on a physical printer — the stylesheet compiles and the on-screen site is unaffected.)
Wide tables on the accessible site now scroll properly on small screens and can be reached with a keyboard. 33 data tables — wallet history, event lists and analytics, leaderboards, marketplace slots and coupons, skills, and the volunteering records (hours, expenses, donations, wellbeing, safeguarding and more) — could overflow the screen on a phone with no way to see or scroll to the hidden columns. Each is now wrapped in a focusable scrolling region that a keyboard user can tab into and scroll, and that a screen reader announces by the table's own heading (which is already translated, so no new wording was needed). This meets the GOV.UK "make tables scroll" pattern and the WCAG reflow requirement. A contract test now makes sure every data table ships inside the scroll region.
The last scattered pockets of English on the accessible site are now translated. A deeper sweep found a handful of phrases still hardcoded in English on otherwise-translated pages: the "Community code" field and its hint on the sign-in, registration and forgot-password pages; the "Communities in this network" heading, its intro line and each "Community link" on a community's home page; the "no messages yet" line and the hidden "Your message" label in a conversation; the "job alerts couldn't be loaded" and "exchange options couldn't be checked" notices; the "we couldn't load the communities" notice on the community chooser; and the word "Important" on every information banner across the whole site. Ten phrases were added to the translation system and machine-translated into nine languages (the banner "Important" reused wording that already existed). The Irish fallbacks were then rewritten in context-aware Irish, with placeholders such as the community name and link preserved. With this, the Ideas, Courses and these scattered strings together clear the bulk of the untranslated text the audit found.
The Courses (learning) section of the accessible site is now translated. Its pages — browsing and course detail, my learning, the lesson player, and the instructor's own screens including the grading queue and course analytics — were almost entirely English built straight into the pages, so speakers of the other languages saw English throughout. That text now lives in the translation system: 79 phrases, machine-translated into nine languages (German, French, Italian, Portuguese, Spanish, Dutch, Polish, Japanese, Arabic) and subsequently rewritten in context-aware Irish rather than routed through Google Translate. Counts like "Based on N reviews" and "This course costs N time credits" use language-appropriate grammar. The Irish course journey no longer falls back to English, while independent native-speaker review remains advisable before describing the wording as professionally certified.
The Ideas (ideation) section of the accessible site is now translated. Its twelve pages — challenges, ideas, drafts, outcomes, campaigns, tags and the management screens — had large amounts of English text built straight into the pages, so speakers of the other languages saw English throughout. That text now lives in the translation system: 36 new phrases, machine-translated into nine languages (German, French, Italian, Portuguese, Spanish, Dutch, Polish, Japanese, Arabic), with those phrases and four existing count messages subsequently rewritten in context-aware Irish rather than routed through Google Translate. Placeholders and Laravel plural-choice syntax are preserved, and independent native-speaker review remains advisable. (Many of the ~155 English fragments the audit counted turned out to reuse phrases the section already had, which is why 36 new ones covered it.)
Three safeguarding messages on the linked-accounts settings page now show in the reader's language. When linking an account was blocked for a safeguarding reason, the explanation was shown in English to everyone. The same wording already existed, fully translated, elsewhere in the platform; those three messages now use it. (The other ~25 status messages on that page were already translated — the audit over-counted.)
Browser-tab titles on the accessible site are now correct and translated. Every community's home page showed the tab title "Accessible - Project NEXUS Accessible" — the community's own name never appeared and the word "Accessible" showed twice. The tab now shows the community's name. Separately, 96 "not found" pages (a missing event, listing, group, and so on) showed a translated heading but an English tab title in every non-English language; the tab title now matches the heading in all eleven languages. Both were fixed centrally, reusing wording that already exists — no new translations needed.
Success and status messages on the accessible site are now announced properly by screen readers. About sixty on-page notification messages were missing the small pieces that make a screen reader read them out and give them a name: roughly 23 weren't announced at all, a dozen success messages weren't flagged as announcements, three information notices were wrongly flagged as urgent, and about twenty had no accessible name. All corrected by a simple rule — a success message is announced; an information message is a named region — including the shared message box used across the marketplace, so several pages were fixed at once.
Long text boxes across the accessible site now show a live "you have N characters left" counter, in the reader's own language. 53 more text boxes — cancellation and decision reasons, comments, appreciations, safeguarding and wellbeing notes, bios, applications, reviews and more — gained the standard GOV.UK character counter that updates as you type and is announced to screen readers. Before, a member writing a note simply hit an invisible limit and their text stopped. The counter's wording is supplied in all eleven languages, with the correct grammar for each (Irish has five number-forms, Arabic six), so a screen-reader user does not hear an English sentence. The browser still enforces the limit for anyone without JavaScript. A floor check stops the counter being stripped off in future.
Screen readers now announce "Error" in the reader's own language, on 36 form fields. The design system the accessible site is built on inserts the English word "Error" before each error message, baked into its own code. On sign-in, registration, password reset, event create and edit, listing create and the message box, a member using a screen reader in one of the ten non-English languages heard the English "Error" immediately before the translated message. All 36 now use the translated word, two that showed a doubled colon ("Error::") are corrected, and a whole-site check stops the English one returning.
Screen-reader and assisted-input gaps across the accessible site, found by the same deeper audit. Hint text sitting under individual radio and checkbox options — the event category filter, the "edit one occurrence or the whole series" choice, the remote-attendance checkbox, the event-registration guest and questionnaire options, and the two message-delete options — was visible on screen but never read out by a screen reader, because nothing connected the hint to its input; it is now announced with the field. On the getting-started page, the photo-upload and "about you" error messages appeared on screen but were likewise not read out with their fields; they now are. Two single checkboxes on the group announcements pages were wrapped in a nameless grouping element, which accessibility checkers rightly flag as a failure — the pointless wrapper is gone. The jobs list skipped a heading level, which breaks the common screen-reader habit of jumping through a page heading by heading, and the volunteering credentials table had no announced name — both fixed. Separately, five fields that ask a member for their own details — an email address, a display name, and three website addresses — now let the browser offer to fill them in automatically, which the accessibility standard requires because retyping your own details is a real barrier for people with motor or memory difficulties. No wording changed anywhere, so nothing needed translating. All 2,227 accessible-site tests pass.
Three faults on the accessible site that could genuinely hurt a member — found by a deeper audit, now fixed.
- Buttons that spend credits or delete things could act twice on a double-click. No money or destructive button on the site had the standard guard against being pressed twice, and the credit-transfer form would accept a blank duplicate-protection code. A quick double-click, or going back and resubmitting, could send a transfer twice. Every such button now ignores a second press, and the transfer refuses a blank code. A whole-site check now makes sure no future money or delete button can ship without the guard.
- The "show off my badges" form never worked. It was the one form on the whole site missing its security token, so every save was silently rejected as "this page has expired". Fixed, and a whole-site check now makes sure no form can ever ship without its security token again.
- Two "done!" messages that lied, and a sign-up page that could lock people out. Marking a group of notifications read, or clearing them all, showed a success message even when the action had actually failed — so the notifications stayed there. And the registration page disabled its own "Create account" button until an outside password-breach-checking service replied; if that service was blocked or slow to answer, nobody could sign up, with no explanation. Both fixed: the success message now only appears on real success, and the sign-up button always works (the password rules are still enforced when you submit).
More accessible-site bugs from the deeper audit, in the behind-the-scenes code.
- Switching language on a half-finished page no longer wipes your choices. When starting a group message, the people you had picked were being silently thrown away the moment you changed language — the list survived for ordinary text boxes but not for multi-select choices. Fixed, and the same fix protects marketplace filters and anything else that lets you pick several things at once.
- Closed a way to trick the server into calling the wrong internal address. On the courses pages, a carefully-crafted web address could make the site send your own request to a different internal destination than intended. Course addresses are now strictly checked to be plain numbers, and anything else is turned away with a "not found".
- A tenant-loading failure now shows a proper error page instead of a blank one. If the site briefly could not load a community's settings, the error page itself broke and showed a bare "Internal Server Error" with no styling or way back. The safety-net that dresses those pages now runs early enough to catch this.
- A sign-in rate limit that wasn't really limiting. The limit on repeated sign-in attempts was counted per email address as well as per computer, so someone trying many different email addresses from one place got a fresh allowance each time — the opposite of the intended protection against guessing which emails have accounts. It now counts per computer, as intended.
- Two pages that could show the wrong thing when the server hiccuped. A member's profile could briefly show someone as "not blocking you" when the block status couldn't be confirmed, and the dashboard could hide the "finish setting up your account" prompt from someone who still needed it. Both now err on the safe side, and an expired login correctly sends you to sign in again rather than showing a half-loaded page.
- Removed two dead files that served no pages but showed up in audits and looked live.
- Not changed this round, and why: making oversized-file uploads show a friendly "that file's too large" message (rather than the current styled error page) needs new wording translated into every language, which is on hold behind the Irish-translation question — so it's grouped with the translation work. The current behaviour is a proper error page with a way back, not a crash, and a suspected temporary-file leak was checked and found not to be real.
Removed
The old accessible website has been taken out of the platform completely. For a while there were two accessible sites: the original one built into the PHP application, and the newer standalone one that has been serving
accessible.project-nexus.iesince 12 August and both community accessible addresses since 14 August. The old one is now deleted — its pages, its routes, its controllers and its build. Before deleting it, all three live accessible addresses and all eleven communities were checked one by one and confirmed to be served by the new site. One thing this costs, stated plainly: until now, if the new site had broken, changing a single line of web-server configuration sent every accessible address back to the old site within seconds. That escape route is gone, because the old site is no longer in the release. Recovering from a bad release now means undoing this change and deploying again — around fifteen to twenty minutes. The owner decided to accept that.Six things the deletion left behind, all of which would have broken something. The removal was reviewed before being published and was found to be incomplete in ways that a passing test run did not reveal. The production image recipe still tried to copy a folder that no longer existed, which meant the live server image could not be built at all. An error-page handler still tried to render a template from the deleted site, which turned a harmless "page not found" from an old bookmark into a hard server error — and did so from inside the error handler itself, which is the worst place for it. Four leftover test files referred to deleted code and would have failed. A documentation check insisted on an index file for the deleted folder and could never pass again. The route-coverage check silently started reporting zero pages instead of failing, which would have made it useless. And a local test-running script listed nineteen deleted files and refused to start at all. All six are fixed. The route-coverage check has been kept alive rather than switched off: the old site's complete list of 707 pages is now frozen in the repository, so the check still notices if the new site ever stops serving one of them.
Two screenshot tools that compared the new site against the old one have been retired, since there is no longer another site to compare against. Their safety checks were not lost: the surviving screenshot tool — which captures signed-in pages that get committed to a public repository — previously had no tests at all, and now has nine, covering every case in which it must refuse to run. It must refuse if the database it is pointed at contains even one real-looking account, if the database is empty, and if the database cannot be inspected. Each refusal was proven by deliberately breaking the check and watching the test fail.
A stale sign-in token file for the deleted site was removed from the local working folder. It was never committed and never at risk of being published, but it was a live credential for an application that no longer exists.
A seventh leftover, found by our own checks rather than by searching. One more tool compared the new accessible site against the old one, and when the old one vanished it did the right thing: it refused to publish a comparison against nothing, and turned a check red. That is exactly the behaviour you want. It has been changed to publish two plain lists — what the old site served on its final day, and what the new site serves — with no verdict at all, because it turned out to disagree wildly with the tool that does this properly: it claimed 406 pages were missing where the authoritative check says none are, simply because it is bad at finding the new site's pages. Publishing both numbers would have been worse than publishing neither. Its own test, which had quietly rotted to the point of being unrunnable and was not part of the automated checks, was rewritten to assert the one thing that matters: that no verdict is ever published here again.
Fixed
The link to the accessible site in every community's top bar is now properly covered by tests, and the two communities with their own accessible address were checked against the live database. The link was already working, and already tested — but only for the ordinary case, where a community uses the shared address with its name in the web address. The case that was never tested is the one that matters to the two communities that have their own accessible address: if that address stopped being passed through, the link would keep working and would silently fall back to the shared address. No error, no broken page, and the existing test would still pass. Three new tests close that. Confirmed on the live server the same day: exactly two communities have their own address stored, both correct, and the other nine correctly have none.
Two test files were checking for wording the application stopped showing. Both the desktop top bar and the mobile menu tests carried their own private copy of the button's wording, and both had drifted: they looked for "Accessibility (alpha)" while the site has said "WCAG 2.2 AA Version" for some time. Three assertions were therefore passing against text that does not appear anywhere. Corrected to the real wording, with a note to copy such values from the translation file rather than typing what you expect them to say.
Added
Dates on the accessible website are moving to three plain boxes — day, month and year — instead of the browser's built-in date picker. GOV.UK guidance is blunt about why: the built-in picker looks and behaves differently in every browser, a phone calendar is close to unusable for a date years in the past, the device decides the format rather than us, and we cannot present the error message in our own style. The goal deadline fields are converted as the reference implementation, and everything needed to convert the rest is now in place: a shared building block, a shared reader that turns three boxes back into the single date the platform already expects, and a counter that stops any new built-in date picker being added while the remaining 36 are worked through. It is deliberately being done in stages — each conversion changes how a form is read when submitted, so doing all 37 at once would be a large change nobody could review. Proven end to end rather than assumed: typing 27 / 3 / 2027 into the real form on a real server stored exactly the right date. The reader is fussy in the right places — it accepts "3" or "03" and ignores stray spaces, but refuses 31 February instead of quietly turning it into 3 March, which is what the underlying date code does if you let it, and refuses a two-digit year rather than guessing the century. A half-filled date is an error rather than silently thrown away. 🔴 Two faults were found by rendering the thing in Irish instead of trusting it: a screen reader would have announced the English word "Error" immediately before an Irish message, and the fix for that produced a doubled colon ("Earráid::") because the design system adds its own. Both are fixed and both are covered by tests.
Long text boxes on the accessible website now tell you how much room you have left, in your own language. Six boxes with a length limit — cancelling a ticket, two event check-in reasons, an agenda cancellation, a group file description and a moderation decision — now show a live "you have 340 characters remaining" count that updates as you type, and is announced to screen readers. The interesting part is the grammar. The design system this site is built on ships that sentence in English inside its own code, so without care an Irish or Arabic speaker would have heard English read out to them regardless of the language they picked. Every one of the eleven languages now supplies its own wording, and — this matters — its own number forms. English has two ("1 character", "2 characters"). Irish has five and Arabic six. The site now picks the correct one for the actual number, so Irish gets its softened and eclipsed forms (charachtar after 1, gcarachtar after 8) and Arabic gets its special form for exactly two. That is not decoration; it is the difference between reading naturally and reading as broken. The counter is an addition, not the enforcement — the limit is still enforced by the browser for anyone without JavaScript. 🔴 Two traps are recorded in the code: the component crashes if a small required element is missing, and silently writes its own English sentence into that element if it is left empty, so both are now filled deliberately. 🔴 And a note on my own testing: my first version of the grammar test passed while the code was deliberately broken, because it compared whole sentences that differ by the number anyway. It now checks the actual word forms, and I proved it fails when the language rules are ignored.
The public Features page now states plainly that a community may not have every module switched on. The page is a catalogue of what the software can do, not an inventory of what any one community has enabled, and members were reading it as a promise about their own site. A prominent accent notice now sits directly beneath the heading, above the maturity key, written without the words "module", "feature flag" or "tenant": it says what the page is, that a community chooses which parts it uses, that a missing item is normal rather than a fault, and that what a member can actually use is whatever appears in their own menus. HeroUI's
alert--accentrenders on a plain white surface with a near-invisible 8%-black border, so the tint and heavier border are set explicitly and were checked in both light and dark themes.The Features page now lists the modules shipped since v1.5 and corrects its stale version and stack claims. Twenty-five entries were missing entirely, including the GOV.UK-based accessible frontend (Beta, live on its own domain), Discover, pluggable map and address-lookup providers, Courses, Podcasts & Community Audio, Clubs & Associations, Public Events, Attendance Rewards, Partner Venues, Collections & Saved Items, Guardian Consent, Donations & Support, Message Translation, AI Agents, choice of AI provider, request-help and offer-favours, care relationships, hour gifting and transfers, the Warmth Pass, trust tiers, the care provider directory, concern reporting, surveys and community projects, the community market, local time-credit redemption, municipality reporting, performance monitoring, the Partner API and developer portal, local advertising, Swiss FADP mode and regional analytics. The member-facing half of the Caring Community layer had been absent altogether, so a roughly twenty-five-route module read as an admin add-on. The maturity chip for each entry is taken from its real default: modules that ship switched off are Preview or "Built, not enabled" rather than GA.
The Features page is now searchable and filterable by category instead of one long scroll. It carries 119 entries, which read as an undifferentiated technical wall. There is now a search box that matches on the visible titles, descriptions and notes — so searching "credits" finds Attendance Rewards, whose key mentions nothing about credits — plus a single-select category chip row, a live "showing X of Y" count, an empty state, and a clear-everything control that only appears once a filter is active. The availability notice and the maturity key stay above the controls so a first-time reader still meets them first. Tabs and accordions were considered and rejected:
/featuresis on the public prerender allowlist, and both patterns unmount the content of every inactive panel, which would drop most of the page out of the SEO snapshot and out of the browser's own find-on-page. Filtering in place keeps the full list in the DOM until the reader deliberately narrows it, and a regression test asserts that every entry renders up-front.The Features page now covers the broker application, the network portal, and the real depth of Events and Hiring. A second application of more than twenty pages had no entry at all: the broker and coordinator workspace, used when a safeguarding or compliance concern is live, covering exchange oversight and match approval, moderation queues, risk tags and watch lists, message review for monitored members, insurance certificates and reporting — with a note that a broker is an operational role in its own right and is deliberately refused the general admin panel. The network and partner portal is likewise now listed, noting that its external-partner sections stay off with the rest of external federation. Events had a single line while shipping thirteen separate areas, so an Event Operations entry now covers ticketing, waiting lists, recurring-event blueprints, agendas, offline QR check-in, accessibility, safety, attendee messaging, templates, analytics and lifecycle history. Hiring gained the pipeline, talent search, employer branding and feed detail it already had, plus a Hiring Bias Audit entry. Merchant coupons, two-factor login with passkeys, and new-community onboarding were also added.
Features page version and stack copy corrected.
meta_title,meta_descriptionandsubheadinghad said v1.5 for three minor releases while the release chip beside the same heading read v1.6.0; the stale version was also present in all ten translated locales. The stack rows now name HeroUI v3 and Vite 7, add the accessible frontend (GOV.UK Frontend 6.4 + Express + Nunjucks), and record that the AI layer supports Anthropic, Google Gemini and self-hosted Ollama alongside OpenAI. The "500+ PHPUnit Tests" entry understated the suite by roughly thirty times and has been replaced with an "Automated Test Suite" entry citing the measured figures (~16,500 PHP test methods across ~1,600 files, ~1,300 frontend suites).
Fixed
Several Features page descriptions described modules that do not work the way the copy claimed. The entries were written from feature-flag key names rather than from the platform's own wording, and have been rewritten against the canonical
module_name_*/module_desc_*copy inadmin_configand the member-facing page copy.member_premiumwas presented as "Premium Membership" offering "paid membership tiers", when the module is Donations & Support — its own pricing title is "Donate", its management page is "My Support", and its canonical description ends "not paid features"; nothing on the platform is placed behind a payment. Hour transfer was described as moving credits between linked household accounts when it exists for a member moving to another community running NEXUS. The Warmth Pass was described as a door-scanned warm-space pass when it is a portable trust credential earned at Trusted tier. Discover was titled "Explore & Discover" rather than the name members see. AI Agents were said never to act, where the canonical description has them running jobs as well as proposing actions. The Caring Community layer was introduced as a "pilot-readiness governance layer" rather than the integrated mutual-aid hub it is. All ten locales were re-translated from the corrected English rather than left holding the earlier wording, and the measured translation-fallback baseline remains zero.Social Prescribing was described on the Features page as platform tooling, which it is not. It read "Information and tooling for community health integration workflows" and carried a Preview chip. There is no referral endpoint anywhere in the platform and its navigation entry is restricted to a single community, so it is neither tooling nor an opt-in module. The entry now describes what it actually is — a managed pathway for a GP, social prescriber or community health worker to refer a patient into the timebank, with a coordinator handling welcome, profiling, matching and follow-up, and outcome data returned to the referrer — and states the published outcomes and the alignment with Sláintecare and the HSE Social Prescribing Framework. The maturity chip was removed rather than changed, because none of the four labels fits something that is live for one community and not switchable by any other; a note carries that distinction instead.
The Features page group icons were assigned positionally.
icons[index]meant that adding or reordering a group silently reassigned the icon of every group after it. Icons are now keyed by group.URL slug terminology now remains technical and grammatically correct across every translated web locale. A detector-blind audit corrected more than 700 strings in German, French, Italian, Portuguese, Spanish, Dutch, Polish, Japanese, Arabic and Irish where automated translation had rendered
slugas an animal, a metal ingot, a fault, a slider or other unrelated words. Labels, validation errors, placeholders and administrator guidance now use established technical terms with language-appropriate articles, gender and sentence structure. This is a contextual translation-quality repair; it does not claim that native-speaker review of every catalogue is complete.The measured React translation fallback baseline is now zero across all ten non-English web locales. The final 46 Italian, Portuguese, Spanish, Polish, Japanese, Arabic and Irish values were reviewed in context: twelve genuine fallbacks now use target-language wording, while 34 proper names, international technical terms, currency figures and true cognates are recorded as intentional identities in both translation audit tools. The same review found a larger detector-blind fault: the German label
KI-Agentenremained embedded in 62 English and non-German AI-agent strings even though the counter could not flag phrases that otherwise differed from English. Those headings, explanations, tab labels and help entries now useAI Agentsin English and natural terminology in French, Italian, Portuguese, Spanish, Polish, Japanese, Arabic and Irish. Irish examples now usem.sh.rather than Englishe.g., and frequency, partner API, tandem and automatic-slug guidance no longer expose untranslated or literal machine wording. This lowers the accepted React fallback baseline from 46 to 0; zero is a measured fallback result, not a claim that native-speaker review of every catalogue is complete.The French web catalogs no longer contain any unreviewed English-identical values. Fifteen administrator strings now use correct French typography and relationship language, including spaced
Total :labels, page-HTML and distance headings, provider interpolation, and recommendation counts instead of sports-style “matchs”. Ten of these repairs were detector-blind formatting or context faults, such as missing spaces around{{count}},{{name}}and{{provider}}. Twenty-eight remaining matches were reviewed as valid French or technical vocabulary—such asOption,Contacts,Notifications,Version,Image,Ad hocand French-identical date/count forms—lowering the accepted React fallback baseline from 79 to 46 while keeping both audit tools aligned.The Dutch web catalogs no longer contain any unreviewed English-identical values. Twenty labels and help phrases now use natural Dutch terminology for AI agents, menu placement, community connectors, podcast episodes, YouTube URLs, Stripe providers, civic-match scores and interviews. This also repairs seven detector-blind machine artifacts, including German
KI-Agentenleaking into Dutch and “master on/off” becoming the person-like “Meester aan/uit” instead of a main switch. Twenty-two remaining matches were reviewed as valid Dutch, proper names or established technical vocabulary, lowering the accepted React fallback baseline from 114 to 79 while keeping both audit tools aligned.The German web catalogs no longer contain any unreviewed English-identical values. Eleven administrator labels now use German wording, including the platform-super-administrator role, private podcast shows and automatic slug guidance. Forty-three remaining matches were reviewed and recorded as valid German or established technical vocabulary—such as
Status,Option,Layouts,Single Sign-On,Open Sourceand format examples—rather than being needlessly rewritten. This lowers the accepted React fallback baseline from 168 to 114 while keeping both audit tools aligned.Counts and dynamic member actions now stay translated for every supported plural form. Likes, comments, recurring-event dates, search-result announcements, named-group links, managed-organisation callouts and expandable-section labels now use context-written wording across all ten web languages. This replaces 142 English values, including detector-blind Arabic, Irish and Polish
zero,two,fewandmanyvariants that could previously switch language only at particular counts. A literal audit also protects 455 exact routes, fake addresses, account examples, filenames, identity/storage protocols, webhook event names, currency notation and infrastructure labels from destructive translation, lowering the accepted React fallback baseline from 701 to 168.Marketplace, onboarding, polling and group recommendations no longer fall back to English for core headings and actions. Search and shipping headings, step-progress announcements, the vote action and recommendation match scores are now localized across all ten translated web languages, replacing 50 visible English values. Translation tooling now also reports exact remaining keys with
--keysand protects the required-field*marker as syntax rather than text, removing ten false positives and lowering the accepted React fallback baseline by 60.Member-facing loading states now remain in the selected language. The caring-community hub, civic digest, ideas area and municipal-feedback journey previously displayed the English word “Loading…” in every translated web locale. All 40 states now use natural progressive wording in German, French, Italian, Portuguese, Spanish, Dutch, Polish, Japanese, Arabic and Irish, lowering the accepted React fallback baseline by 40.
The partner-timebank workspace now uses translated navigation and actions without corrupting the Credit Commons product name. Account fallbacks, overview and settings sections, partnership and neighbourhood pages, analytics, breadcrumbs, create/open actions and optional-step labels are now localized across all ten translated web languages. This replaces 177 English values, all of which were short labels invisible to the fallback detector. The audit also protects
Credit Commonsas an exact product name, removing 33 false positives across partner and administrator catalogs without sending the name through a translator and lowering the accepted React fallback baseline by 33.Sentry error triage now happens on its own every night, instead of only when the owner asks for it. Every look at Sentry until now started with the owner asking for one, which made triage sporadic and made an agent's account of "what Sentry says" impossible to check. There is now a script that fetches the unresolved issues, ranks them by how many members they hit rather than by raw volume, and writes both a machine-readable queue and a plain-English report — so the list can be read without an agent at all. A nightly task then works that queue: it fixes small, clearly-caused crashes, adds a regression test for each, and commits only when the local checks pass; if they fail it saves the attempt as a patch file, puts the code back, and reports the failure rather than hiding it. Nothing is ever deployed by it. Two guards are written into the script rather than left to instructions: any issue whose text touches money, permissions, safeguarding, consent, deletion, encryption or migrations is classified
sensitiveand cannot be fixed automatically — it is diagnosed and handed to the owner; and the only way it can close an issue is Sentry's "resolve in next release", so an issue closes when the fix actually ships and reopens by itself if the error happens again. That means "resolved" reflects what production did, not what an agent believed. Decisions are kept in a committed ledger, so judging something as accepted noise is reviewable and is not re-investigated every night — and an issue previously marked as fixed comes straight back into the queue if it recurs, which is the honest check that the fix worked. Verified against live Sentry when built: both open issues were correctly classified as sensitive, and the GDPR data-request alarms were recorded as the owner's own admin testing so they stop being re-raised.Matching now uses relationship language rather than sports terminology across every web locale. The nine remaining translated journeys now localize 207 labels covering mutual matches, actions, relevance and feasibility scoring, trust, proximity, availability, reviews, dismissal reasons, preferences and notification cadence. Context review also replaces Google-style sports translations such as German “Spiele”, Italian “partite”, Spanish “partidos”, Dutch “wedstrijden”, Polish “mecze” and Japanese “対戦” with language appropriate to matching people, listings, groups and opportunities. This removes 36 measured React fallbacks while correcting substantially more detector-blind wording.
Knowledge-base navigation, counts, search feedback and failure states now stay in the selected language. Breadcrumb labels, article and category plurals, result announcements and searching states are localized across all ten web languages, including Polish, Arabic and Irish plural forms. Sixty older dotted-key error, empty-state and feedback values in French, Dutch, Polish, Japanese, Arabic and Irish now match their localized nested equivalents instead of retaining hidden English copies. This removes 59 measured React fallbacks and another 60 detector-blind English values.
Irish matching pages no longer mix English into core actions and preferences. Mutual matches, listing and event sources, remote participation, messaging and joining actions, relevance and feasibility signals, trust, proximity, availability, reviews, dismissal reasons, matching preferences and notification cadence now use natural Irish wording. This repairs the full English-identical cluster found in that journey, including nineteen single-word labels that the fallback counter deliberately cannot detect.
The translation audit now distinguishes administrator copy from values that must remain exact. OAuth client IDs, example inboxes, robots.txt rules, route and tenant slugs, Apache/Plesk, Gmail API and Google Gemini are protected as operational values instead of being sent to a translator. Genuine labels beside them are localized: Meilisearch status and queued-job titles in German, automatic recaching in Portuguese, component score and platform-super-administrator wording in Dutch, and the webhook URL label in Japanese. This removes 129 false or real fallbacks from the accepted React baseline while keeping configuration examples usable. Both the regression checker and translation helper now use the same reviewed protections for these values.
The feature directory no longer falls back to English in translated “coming soon” pages. Job vacancies, caring communities, knowledge bases, ideation challenges, regional points, group exchanges, member directories and the AI assistant now use natural terminology in all ten web languages, with community-centred Irish wording. The Italian upload hint also localizes its maximum file size. This removes a further 81 entries from the accepted React fallback baseline.
Core navigation and sign-in translation debt is now measured accurately, and verification status is fully translated. Search-result announcements—including Irish and Arabic plural forms—plus email, phone, identity, DBS and administrator verification badges are now localized across all ten web languages. Authentication examples such as
you@example.com, masked passwords, backup-code shapes and the deliberately weakP@ssw0rd!example are now protected as functional literals instead of being misreported or sent to a translator. The same protection covers age bands, the Command symbol, Project NEXUS and its exact AGPL copyright notice. This removes 150 entries from the accepted React fallback baseline without corrupting form examples or legal text.There are now pictures of the accessible website in the project, including the pages you have to be signed in to see. Twenty of them: ten pages at desktop size and at the narrowest phone width, covering the dashboard, listings, a listing, messages, the time wallet, members, profile settings and exchanges. There were none before, and not for want of trying — every signed-in picture would have contained real members' names, and this project is public. Now that we have throwaway data, the people in them are invented. The tool that takes them refuses to run unless every account it can see is invented and the page it captures shows the invented member's name; either check failing stops it dead, and those two checks are the only thing standing between that folder and a public leak. Each picture is recorded with whether the page had to be scrolled sideways at phone width, which is an accessibility failure — none of the twenty did.
The connections between the accessible website and the platform are now tested against a running platform, rather than read off the code. There is a file recording 696 of these connections, but it was written by reading source code, so it could only say "this call exists and the platform has a matching address". It could not say what actually comes back, or whether an address hands data to someone who is not signed in. 120 of them are now called for real, twice each — once signed out, once signed in — and what came back is written down. 112 correctly refuse a signed-out caller, none fail with a server error, and every answer is in the expected shape. 22 correctly refuse because the test community has those features switched off, which is recorded separately so nobody reads the file as "everything works". Three things it first flagged turned out to be the check being too strict, and each is now written down as a deliberate exception with the reason: the public statistics address is meant to be public (checked — it gives only totals, no personal information), and two others correctly return a spreadsheet and a calendar file rather than data.
Podcast details and studio fields no longer switch back to English across the ten translated web languages. Host bylines, episode and reaction counts, show attribution, chapter navigation, podcast-studio headings, media-status labels and artwork/audio URL fields are now translated with their interpolation placeholders intact. This removes another 112 accepted fallbacks from the React translation baseline. The A–Z sort label is now treated as a functional ordering symbol rather than text to send through a translator.
Broker moderation and mobile resources no longer fall back to English in otherwise translated journeys. The broker workspace's moderation queue, feed, comments, reviews, reports and safeguarding navigation is now translated across all ten web languages, removing 133 accepted fallbacks from the React translation baseline. The native app's Resources area is now fully worded in German, Spanish, French, Irish, Italian and Portuguese, removing a further 108 English fallbacks while preserving interpolation values. Translation tooling now also recognises the broker email example and the Command-K shortcut as functional literals rather than text to translate.
Translations now protect technical identifiers and reach the accessible website's live interactions. The platform now rejects corrupted translation encoding across React, PHP and mobile catalogs, keeps peer
slugidentifiers and literal examples unchanged in every admin language, and repairs the six mobile AI-chat catalogs that contained broken accented characters and English-only results and feedback controls. The accessible website's partner-community hub, event-form validation, session-expiry dialog, screen-reader countdown announcements and bounded text-area character counts now use the visitor's selected language; the countdown uses each language's own plural rules. The accessible and PHP untranslated checks now share one reviewed invariant allowlist, and both translation utilities refuse the bulk Google route for Irish so a provider acceptinggacannot silently weaken Irish copy.We can now test the accessible website against throwaway data instead of a copy of real members. Every check on the new accessible site has until now run against the ordinary local database, which is a copy taken from the live platform — real names, real email addresses, real messages. That quietly blocked three whole kinds of testing: we could never try deleting anything, because it would have destroyed a real record; we could never save screenshots into the project, because they would publish real members' names in a public repository; and uploads wrote real files. There is now a separate, disposable copy of the platform — its own database, its own uploaded-files storage, its own web address — filled with four invented accounts and one invented listing, and nothing else. One command builds it, one command wipes it back to a clean state, and it can be thrown away entirely at any time. Signing in and reaching a member's dashboard through it has been done for real, not assumed. It cannot reach the live platform: the file says so in writing, and must never be added to the deployment configuration. 🔴 Two traps were found by building it, and both are now guarded rather than remembered. The platform reads two different names for "which database", and prefers the one we had not set — so on the very first run the setup wrote into the database the automated test suite owns, instead of the throwaway one, and reported success while doing it. Nothing of value was harmed (one row, in a database that is rebuilt from scratch by every test run), but it is exactly the collision this work exists to prevent. The setup now asks the platform itself which database it has connected to and refuses to write anything until the answer is the throwaway one — reading the container's settings is not good enough, because the settings said one thing and the platform did another. There is also a check that every account present looks invented; if a real-looking address ever turns up, it says so and warns against publishing screenshots.
The accessible website now has complete wording ready in all eleven languages for five blocked accessibility improvements. This adds the character-count announcements used by text areas, three-part date labels and errors, the session-expiry warning and countdown, member-friendly partner-community wording, and event-form errors. Irish was written directly rather than sent through the automated translator, including its five different number forms; the other nine translations were generated through the existing pipeline and then reviewed for grammar, placeholders and context. The wording is prepared for the separate feature work and does not yet change any page. Internal “federation” language was replaced in these new member-facing phrases with “partner communities”.
The admin panel now has a documentation link pinned at the bottom of its sidebar, and so does the sales site's main menu. Administrators are the people most likely to need the platform documentation and had no way to reach it from inside the admin panel. It sits in the small pinned strip at the very bottom of the sidebar — always visible, never scrolling away, and deliberately not placed beside "Help FAQs", which is the tool for editing the help articles members read and is a completely different job. It works collapsed as well as expanded: when the sidebar is narrowed to icons the label disappears but the link stays reachable, with the name still announced to screen readers, and a test covers both states so a future layout change cannot quietly drop it. It is a plain link rather than an internal one, because every other entry in that sidebar is an internal page and routing to an external address would have produced a dead admin path — the code says so, so nobody "tidies" it back. Opens in a new tab with the usual security attributes. Translated by hand into all eleven languages. On the sales site at
project-nexus.ie, "Docs" now sits in the main menu alongside Platform, Features and Live App. That one is deployed and live — checked on the real site at both desktop and phone widths: visible in the header on desktop, and inside the menu on a phone as a full-width entry comfortably above the minimum tap-target size, with the destination confirmed to load. 🔴 Five links on that site still named the old repository and were repointed at the same time, including the licence source link and the NOTICE link, which should not depend on a forwarding rule.The footer now links to the documentation site, and the sentence beside it was cut down to make room. The platform has a full documentation site and nothing in the app pointed at it, which is a missed opportunity: for a community deciding whether to adopt this platform, published documentation is one of the strongest signals that it is a serious piece of software. The link is deliberately not prominent. It sits in the small release strip at the very bottom of the footer, alongside the existing Features and Changelog links — the "about this platform" line, not the member navigation. That was a judgement call worth recording: module guides and an API reference are for communities evaluating us, for community administrators and for developers, not for a member offering an hour of gardening. Putting "Documentation" in the main menu would make the product feel like a developer tool, so it is not there, and it is not in the Help Centre either, because the Help Centre answers "how do I offer an hour?" and mixing the two would confuse the people it exists to serve. It opens in a new tab, keeps the security attributes that stop the new page tampering with ours, carries the small external-link arrow, and tells screen-reader users it opens in a new tab. The sentence that shared the line was long — "Project NEXUS is actively evolving, with feature maturity shown transparently across the platform" — and is now simply "Always evolving.", which says the same thing in two words and leaves room for the new link. The address is on its own domain on purpose, and there is a comment in the code saying why: it used to be built from the GitHub repository's name, so renaming the repository broke every link to it with no forwarding. All wording is translated into the other ten languages by hand, not machine-translated — short technical words are exactly what the translator mangles, as the "Windows"/"Mac" and "React Native" corrections recorded below show. Verified in a running browser rather than assumed: the strip reads "Generally Available (v1.6.0) — Always evolving. · Features · Changelog · Documentation", and the link carries the right address, opens in a new tab and has both security attributes. A new test fails if the link disappears or stops opening in a new tab; 34 footer tests and 108 tests across the four layout suites pass, typecheck is clean, and the translation gate confirms no language was left behind. 🔴 The same link on the sales site at
project-nexus.ieis NOT done, because that site's code is not in this repository — it was removed in72889b550and no copy exists on the platform server. It needs to be done wherever that site now lives.The "Get the app" page now explains what a native app actually is, says the real one is nearly finished, and answers "why doesn't this work on Apple?" — and an audit of
mobile/settled what we can honestly promise. The page previously described four kinds of app, one of which was the retired web-wrapper wrapper. That was both confusing (three things to choose between) and out of date, because the real native client is the Expo/React Native app inmobile/— a different codebase, at version 1.2.0, 257 screens, 217 test files, and still being worked on as of 11 August. What the audit established, so the page does not over-promise:mobile/app.jsonconfigures both platforms — an iOS bundle identifier with permission strings and associated domains, and an Android package — so it is genuinely one app for iPhone/iPad and Android. But only the Android release path actually exists:eas.jsonhaswebsite(signed APK) andproduction(Play Store bundle) profiles, an EAS-managed keystore and Android push credentials, and the only build/submit scripts inpackage.jsonare Android ones. The iOS half of the submit config is still the literal placeholderAPPLE_APP_STORE_CONNECT_APP_ID,docs/DISTRIBUTION.mddocuments Android channels only, and Apple requires a developer account and reviews every app before publication. The page therefore says Android is closest and iPhone follows shortly after, and publishes no download link for either. The retired web-wrapper wrapper is deliberately not mentioned to members because that project was abandoned; the page code says so in a comment, and a test asserts the word "retired web-wrapper" does not appear, so it cannot creep back in unnoticed. The Apple explanation is the other half. Telling members "it does not work on Apple" without a reason reads as our fault, so there is now a "Why is it harder on Apple devices?" section giving Apple's three rules in plain words: there is no one-tap install on Apple at all (the browser-provided install button simply does not exist in Safari, which is why the withdrawn banner could never have worked there); on iPhone and iPad only Safari can do it, and a Mac needs Safari 17 or newer; and Apple keeps the saved app's data separate from Safari, so it opens logged out and you must sign in again inside it — which is confirmed by our own code, since the session token lives in browser storage that Apple does not share with the saved app. The section ends by pointing at the native app as the real answer for Apple users. 🔴 Not verified on a real Apple device — nobody here has one to hand; the three points above are Apple platform rules and our own storage design, not a reproduction of a member's failure. 🔴 A separate, real defect was found while checking this and is NOT fixed here: the install manifest served forapp.project-nexus.iewith no community in the address comes back withstart_urlandscopeof/admin/, because the platform's own master community has the slugadmin. Anyone installing from that address would get an app that opens the admin path and treats every ordinary page as outside itself, opening it in the browser instead. Community domains are unaffected —minehead-and-coast.timebank.globalcorrectly returns/. Also noted for separate work: the public Features page still describes the native app as "iOS and Android builds from the same React codebase via retired web-wrapper", which is no longer whatmobile/is.There is now a "Get the app" page that tells members the truth about which devices can install the app, and the install banner that promised otherwise has been taken down. The banner across the top of every page offered a one-tap install. On an iPhone, iPad or Mac that one tap cannot deliver — Apple only allows it in Safari, and it is unreliable there at the moment — so a large share of members were being offered something that would not work for them, on every single page, with no way to explain why. The banner is gone (its code is kept, unrendered, so it can come back once Apple installs work). In its place, the "Install app" item in the profile menu and the mobile menu now say "Get the app" and lead to a new page at
/install-app, which does four things a prompt cannot. It states plainly what works today — Android with Chrome, and Windows with Chrome or Edge — and what does not: iPhone, iPad and Mac, which we say we know about, are working on, and will update the page about. It explains the four kinds of app in ordinary words rather than jargon: using it in a browser with nothing installed; saving it to your home screen (the page says "the technical name for this is a PWA, short for Progressive Web App" rather than assuming anyone knows); our own Android app, which it describes as a retired web-wrapper app and openly says is built but not released; and Google Play and the App Store, which we are not in yet. It gives numbered, per-device instructions with the reader's own device pre-selected, and warns on the Apple tabs that this is the path currently misbehaving. And it only shows a one-tap install button when the browser has genuinely offered one — otherwise it shows instructions, so the page can never promise a button that does nothing. 🔴 No download link is published for the Android app, deliberately. The address the app's own updater points at (/downloads/nexus-latest.apk) was checked and does not serve a file — one host returns "not found" and the other quietly returns the website instead. Publishing it would have handed members a broken download. Three things were found while wiring it up. The new address had to be added to the three separate lists of "path segments that are not a community name" (React, Laravel and the accessible site, whose own test requires the last two to match exactly) — without that,/install-appon the shared address was read as a community called "install-app" and showed "Community not found", which is exactly what happened on the first attempt. The old "Get App" entry in the Explore menu pointed at/mobile-download, an address this app has no page for, so it has been repointed at the new page. And the page is now listed in the sitemap so members can find it by searching. All wording goes through the translation system in all eleven languages. 🔴 The machine translations needed hand correction, and this is worth recording: the device labels came back as ordinary nouns — "Windows" became Fenêtres, Finestre, Ramen, ventanas, Okna and 窓, and "Mac" became Impermeable (Spanish for raincoat) and Prochowiec (Polish for trench coat). Brand names are now forced to stay in Latin script in every language, and two English labels were reworded because they translated as verbs ("Working today" became "I work today" in four languages). Verified by using the page in a browser at desktop and phone widths in both light and dark: the correct device tab is chosen automatically, switching tabs works, the Apple warning appears where it should. 10 new page tests, 17 tests in total across the page and the menu entry, and the suites touched by the change — 339 tests across layout, routing and the install components — all pass.
Changed
- The old accessible site is now recorded as historic, and no longer the thing we copy. Until today the old site was treated as the definition of "correct" — if the new one differed, the new one was assumed wrong. That has been reversed by decision: the old site is falling behind, so the new one now follows GOV.UK guidance for how pages should look and behave, the React app for what members can actually do, and the platform's own interface for the rules. A difference from the old site is no longer automatically a fault; it may simply mean the old site is behind. The old site stays switched on and serving members, and retires only once we are certain nothing is still needed from it. Written into the three places an AI assistant or a person would look, including a plain note that the route comparison list is now a coverage record rather than an alarm.
- 🔴 Corrected a readiness score I had inflated. I recorded 787 out of 1,000 when the honest figure was 730. The mistake: I awarded points for fixing four real faults, but those faults had never been counted as marks lost in the first place — so repairing them restored accuracy, it did not earn credit. The automated check could not catch it, because it only verifies the column adds up to the stated total, which it did. The rule is now written at the top of that document: a row's score is its maximum minus the deductions listed against it, and nothing else may raise it.
Changed
- Updated the GOV.UK design system the accessible website is built on to the current release (6.4.0, published days ago). Everything was re-checked afterwards rather than assumed: 2,142 automated checks, the accessibility scan on 24 real signed-in pages (still completely clean), high-contrast mode, phone width, dark mode, and the visual comparison against the old site — which actually got closer, from about 13% of pixels differing down to 10%. 🔴 The instruction file said we were on 6.1.0 when we were really on 6.3.0; that note is corrected, with a warning not to trust a version written in prose.
Fixed
The build server went red because a type error in a test had been fixed. One of our safety nets counts known type problems in test files and only ever allows that count to go down — so when a fix reduces it, the new lower number has to be written down in the same change, otherwise the net still expects the old higher one and stops the build. The events broadcast-preview fix repaired one such problem in
EventCommunicationsWorkspace.test.tsxwithout recording the improvement, so the count is now locked in at 1,933 across 648 files, down from 1,934 across 649. Nothing about the product changed. Reproduced on this machine first and got the identical numbers to the build server, so the recorded figure is not a local artefact.The branding guard on the accessible website could not run on the build server, and had never really run there. That guard exists to stop the licensed UK government typeface getting into the accessible site — using it would wrongly imply the site is an official government service. It used to look only at the page templates, which cannot contain a typeface at all, so it reported "passed" while checking nothing. It was changed earlier the same day to inspect the finished stylesheet instead, and to stop with an error if that stylesheet is missing, because "the file isn't there" must never be reported as "the check passed". That immediately turned the build server red — correctly — because the build server was running the guard before it built the stylesheet, and the stylesheet is produced by the build rather than stored in the project. The build order is now the right way round. 🔴 Two things worth stating plainly: this broke the build for a few hours on any change touching the accessible site, and one commit in between looked fine only because it did not touch that site so the job was skipped, which hid the pattern. And the ordering mistake was invisible for as long as the guard checked nothing — which is exactly the empty pass the change was written to stop. Checked both ways on a real machine: with the stylesheet deleted the guard refuses; after building it, it passes.
Saving your profile said it had failed when it had actually saved — and blamed your photo. On the accessible website, saving your profile settings also wrote your newsletter choice every single time, and if that one step failed the whole save was reported as failed. Your name, your photo and your privacy settings had all saved perfectly by that point, but the message said "Your profile could not be updated" — so the natural thing to do is try again, and get the same message. It was worse than that: the message it picked was "Upload a JPG, PNG, GIF or WEBP image smaller than 10MB", so you were told your photo was the wrong sort of file when your photo had uploaded fine and something else entirely had gone wrong. This was not a rare edge case: the newsletter step fails on any installation where the newsletter consent record was never created, and nothing in the project creates it. Three fixes. The newsletter choice is now only written when you actually changed it — so nothing fails when you did not touch it, and an explicit sign-up is still always recorded. The platform now replies "that value is not recognised" rather than "the server broke", which was raising false alarms in our error monitoring and left the website unable to tell a bad value from a real outage, which is exactly why it assumed the worst. And the photo is only blamed when the photo is genuinely the thing that failed. 🔴 Worth checking on the live platform: whether that newsletter consent record exists there. It does not exist in our local copy, and I have not assumed one follows from the other.
On the accessible website, opening another community's page while signed in showed you your own data under their name. If a member of one community followed a link to another community's dashboard, wallet or member list, they got a normal-looking page — but the information on it was their own, from their own community, wearing the other community's address. Every link on that page then kept them there, so they stayed in that mixed-up state until they edited the address bar by hand. Nothing leaked, and that was checked rather than assumed: the member records and wallet balance shown were the signed-in member's own throughout, because the platform works out who you are from your sign-in, not from the address. Members are now sent back to their own community's version of whatever page they asked for. Browsing another community's public pages while signed in still works, as it should, and partner-community browsing is untouched.
Previewing an event announcement failed for the organiser. Before sending a message to people involved in an event, the organiser gets a preview showing how many people it will reach. That preview was being thrown away by our own app: the web app insisted the server report a count for all four groups of people (registered, on the waiting list, attended, did not attend), but the server sensibly only counts the groups you actually asked about. So previewing for one or two groups — the normal case — failed, and the organiser saw an error instead of the numbers. It broke on the live site on 2 August. Two things worth recording: the phone app already had this right, so only the website was wrong; and there was no test at all on this preview, which is exactly why the mistake went unnoticed. There are now three, one of which checks that the fix did not also make the app accept a group name that does not exist.
The "you have made too many requests" page on the accessible website crashed instead of appearing. The site limits anyone to 100 page requests every fifteen minutes. When somebody reached that limit, the page meant to explain it politely could not be built at all, so they got a bare error instead — and the platform recorded a fault. The cause: that limit is checked very early, deliberately, before the site has worked out which language the visitor reads, and all six of the site's error pages need the language to be ready before they can be written. So the error page became a second error. This was happening on the live site (spotted 13 August, on a scanner probing the site, but an ordinary member browsing quickly hits the same limit). Every error page — including "page not found" and "something went wrong" — can now always be built, in the visitor's own language, no matter how early in a request things go wrong. The fix is tested from both directions: the test proves the page now appears, and also proves it would have failed before, so the problem cannot come back unnoticed.
Our own alarms were burying the alarm list. Six of the platform's nightly self-checks (overdue data-protection requests, missing backups, stuck Stripe payments, the exchange-completion target, and the weekly "the alarms still work" heartbeat) write their message with live numbers in it — a count, an age in days, a percentage. The error tracker groups by that message text, so the same unresolved problem appeared as a brand-new alarm every single night. Four overdue data-protection requests had produced six separate alarms in six days. That is not just untidy: a nightly-respawning alarm cannot be snoozed, shows no history of how long it has been going on, and pushes genuine new faults down the list. Each check now declares its own fixed identity, so one ongoing problem is one alarm whose age you can read at a glance. A test refuses any future self-check that forgets to do this.
The accessible website was being killed mid-sentence on every deploy. When the platform switches between its blue and green copies, the old one is told to stop. The site had no instruction for what to do when told to stop, so it exited instantly — cutting off any page it was part-way through sending, and dropping its connection to the session store without closing it. Members reading a page at that moment would get a truncated one. It now stops accepting new visitors, lets the pages it is already sending finish, closes the session connection properly and only then exits — with a ten-second cap so it can never hold up a deploy. 🔴 Verified for real, not just in tests: sending the stop signal to the running container logged "closing server to new connections" then "Shutdown complete".
Set an explicit limit on how much data a form can post (256KB). It was previously relying on an invisible default buried in a library, which matters here because the code keeps a full copy of every submitted form in memory. File uploads are unaffected — they go through a separate route with their own 10MB limit.
The page where someone is most likely to be lost had no way to get help. On the shared address — the page that asks you to choose your community — the entire footer was being removed, taking the Help centre, Contact, Knowledge base and About links with it. That breaks an accessibility rule (WCAG 2.2, "consistent help") which says a way to get help must sit in the same place on every page, because people rely on remembering where it is. Help and Contact now appear there, in the same footer position as everywhere else. Community-specific links still only show once you are inside a community, which is correct. 🔴 The old site has the same gap; this deliberately does better, per your decision that GOV.UK guidance wins.
The "you're about to be signed out" warning could fire at the wrong time, and nobody would have noticed. The countdown was told the session lasts 30 minutes by a number written into the page's own script, entirely separately from the real setting on the server. Change the server's session length and the warning would quietly start appearing minutes too early — or worse, after the session had already ended, so the first a member knew about it was being thrown out mid-form. It now reads the real figure from the server on every page, with a safety check so the warning can never be scheduled after the session has already gone. The server was already working this figure out and passing it to the page; nothing was reading it.
A member whose session ran out was told "You have signed out", with a green success tick. They hadn't signed out — their session expired — and it certainly wasn't a success. The timeout has always told the server it was a timeout; the server just ignored it. It now says "Sign in to use this page" and presents it as something to act on rather than something that went well. 🔴 Uses wording that already exists in all eleven languages, because adding new wording is currently blocked on Irish translation.
Screen readers now announce straight away when a form has a problem. GOV.UK's rule is that when a form comes back with something to correct, the page's name must start with "Error:" — because that name is the very first thing a screen reader says when the page loads. Without it, someone using a screen reader is told the page name, then has to go hunting to discover anything went wrong at all. Neither accessible site did this. Now the sign-in, contact, and create-an-event/listing/group pages all announce it, translated: in Irish the page name begins "Earráid:". It is worked out in one place rather than sprinkled across the thirty-odd places a form can fail, so the next new form gets it automatically. 🔴 Deliberately not applied to pages that merely failed to load something — there is nothing for the reader to correct there, and crying "Error:" on those would be noise.
Two more question pages got a "back" link, in the standard place at the top: the password-reset page and advanced search. 🔴 The sign-in and registration pages deliberately have none — there is no previous step to go back to, and GOV.UK back links are for stepping back through a process.
The "hide cookie message" control is now a button, not a link. GOV.UK specifies a button, and the difference is not decoration: a link is announced as "go somewhere", while this control's actual job is to dismiss a message. It still works with JavaScript turned off.
🔴 The new accessible website was asking browsers for the official UK Government typeface. Its compiled stylesheet named GDS Transport 63 times. That font is licensed for GOV.UK services only, and this platform is explicitly not one — the project's branding rules forbid it by name. The old site sets a plain system font and this one never did, so the design system's default flowed straight through. Nothing looked wrong, because the font file is never actually served and text fell back to Arial — which is exactly why it went unnoticed. Both sites now render identically in Arial, confirmed in a browser. 🔴 The branding check that is supposed to catch this reported "passed" the whole time, because it only ever looked at page templates and a font lives in the stylesheet. It now checks the compiled stylesheet too, refuses to pass if that file is missing, and I proved it can fail by putting the font name back and watching it catch it.
Keyboard-only members can definitely submit forms — now proven rather than assumed. This was recorded as unproven for months. It works three ways: pressing Enter in a text box submits the form, and a submit button reached by keyboard responds to both Enter and Space. 🔴 An empty form looks unresponsive, but that is the browser correctly refusing to send an incomplete form, not a fault.
A visual comparison set between the two accessible sites now exists — 32 full-page comparisons, same member, same page, at desktop and phone widths, with an automatic pixel-by-pixel difference measurement. Desktop differs about 6%, phone about 20%. 🔴 The phone figure is not a defect: the two are laid out identically (same text size, spacing and column width), so a single 23-pixel offset near the top pushes everything below it down and counts as "different". Treat these numbers as a baseline for spotting future changes, not a quality score. 🔴 The images themselves are deliberately not stored in the repository: the local database is a copy of real data, so the screenshots contain real members' names.
Filter rows on five pages were unusably small targets, and rendered as bulleted lists instead of a filter row. The wallet's "All / Earned / Spent / Pending" filters — and the same control on Exchanges, your Network and both Matches pages — were 19 pixels tall against an accessibility minimum of 24, and packed too closely together. That is a real failure of an accessibility rule (WCAG 2.2, target size), and it lands on the people this website exists for: anyone with a hand tremor, limited dexterity, or using a touchscreen. The cause was simple — the styling for that control was never carried over from the old site, so the browser fell back to a plain bulleted list. The old site's layout is now in place and the links are 34 pixels tall. 🔴 This deliberately goes further than the old site, which fails the same rule; matching it exactly would have kept the fault. Found by running an automated accessibility scan against real signed-in pages for the first time.
The sign-up page's tests were switched off, so the consent-tickbox fix had nothing guarding it — they now run. That test file was on the list the build deliberately skips, and it turned out to be failing for a plumbing reason, not a real one: the test stood in for the shared network module but forgot one of the things it hands out, and the sign-in-with-Google buttons on the page ask for exactly that. The test framework treats a missing piece as fatal, which wiped out the whole page render — so any check needing the full form failed, while checks that only needed the first step passed. Supplying that one missing piece makes all six original checks pass. The file is off the skip-list, the allowance was reduced in the same change so it cannot quietly refill, and a new check now guards the two faults the coordinator reported: that the consent tickbox is not drawn at the smallest size, and that the Terms and Privacy links open in a new tab. 🔴 That new check was verified by deliberately putting the old fault back — it failed, as it should, and passed again once restored. Worth stating because a check that cannot fail is worse than none, and my first attempt at it was exactly that.
Another 34 page names on the new accessible website now appear in the reader's language. Courses, Blog, Polls, Hashtags, Blocked members, Announcements, Discussions, Invite members, the ideation pages and the community chooser all had their name written into the code in English. Checked in a browser: Blog now reads "Blag" in Irish, Polls "Pobalbhreitheanna", Blocked members "Comhaltaí bloicthe". Only names whose translation is word-for-word identical were changed, so nothing shifted meaning. 🔴 22 remain and are deliberately left alone, with the reasons written into the check that tracks them: the specific "Event not found" / "Listing not found" style names would become a vaguer "Page not found" if switched to the only translation that exists, which would tell a member less; and a handful of others would be silently reworded. Those need their own translations added, which is a separate piece of work.
Error pages on the new accessible website were always in English, in every language. "Page not found", "You do not have permission to view this page", "Sorry, the service is unavailable" and "Too many requests" were written into the code in 75 places across 22 files, so a member reading the site in Irish, Polish or Arabic hit an English page exactly when something had already gone wrong. They now use the wording that already existed in all eleven languages — checked in a browser: the "page not found" title reads "Níor aimsíodh an leathanach" in Irish. Two of them also read better now: the bare word "Forbidden" is replaced by "You do not have permission to view this page". 🔴 These lookups are deliberately written to survive a missing translator. Several of them fire from error handlers, rate limiting and permission gates that can run before the page's language is worked out; asking for a translation there without a guard would crash the error handler itself and turn a handled "not allowed" page into a broken one.
🔴 Correction to a figure reported earlier in this session. The count of "253 hardcoded page titles" was wrong — it was over-counted. Many of those are a deliberate arrangement, not a mistake: the page passes an English fallback and a separate translation key, and the layout does the translating. The jobs section uses that arrangement for all 16 of its pages, and a test asserts the exact shape. Acting on the bad number, those 16 were "fixed" and the arrangement broke; that has been undone. The check that tracks this debt now understands the pairing and ignores it, so the honest remaining figure is 56, not 253 — and it still can only go down.
Three more gaps between the two accessible websites, closed. 1) Your own profile was missing its "Recent activity" and "Availability" sections — every other member's profile showed both, so you could see everyone's availability except your own, and had no way to check how what you had published actually reads to other people. The old site shows both. The shaping code is now shared between the two pages so they cannot drift apart again. 2) The AI assistant page looked usable when no assistant is configured. The old site says plainly "The AI assistant is not available at the moment"; the new one said nothing, so a member would type a question and get nowhere. It now asks the platform whether an assistant is actually set up — which is a different question from whether the community has switched the feature on — and says so when there isn't one. If that check itself fails it assumes the assistant is available, so a momentary blip never wrongly tells someone the feature is gone. 3) Form section titles were not proper headings. On the create-a-listing, edit-a-listing and create-a-group forms, headings like "What kind of listing is this?" and "Who can join this group?" were plain text rather than headings, so someone using a screen reader could not jump between the sections of a long form. They are now headings, matching the old site and the GOV.UK pattern. The "Partner communities" page also had seven pieces of English text baked in, including two section titles that differed from the old site's wording; those now use the existing translations.
Two faults on the sign-up form, both reported by a coordinator doing a real sign-up. First, the "I agree to the terms" tickbox was almost invisible on desktop and on iPhone. It was being drawn at 12 pixels — the smallest size our own wrapper offers — which is the last place to save space on a control that carries legal weight. It is now 20 pixels with a clear coloured fill, so a tick is unmistakable. The newsletter tickbox next to it got the same treatment. Second, opening the Terms or Privacy link wiped the whole form. Sign-up is a four-step form and every answer lives in the page's memory, so leaving to read the terms and coming back threw all of it away — and the step it happens on is the one holding the password, so nothing could safely have been saved and restored either. Those links now open in a new tab, leaving the part-finished form untouched. 🔴 This fix is not covered by an automated test: the sign-up form's test file is on the known-failing list that the build deliberately skips, for reasons that predate this change, so nothing would catch a regression here. Worth fixing that suite next.
A latent fault meant admins whose authority is a boolean flag would never have been told a new member had registered. 🔴 Correction, and it matters: this was first written up here as the cause of a live report from a coordinator who said she received no alert. It was not. Checking the production delivery records afterwards showed her email was delivered and her bell notification was created and opened; her account carries
role = 'admin', which the original query already matched, and across the whole platform no account was actually being missed. What was really wrong is a separate thing, fixed in the entry below. The fault described here is real but had not yet bitten anyone, and the fix is kept because it will: the code chose who to tell by reading the single word in therolecolumn on each account. On this platform that word is frequently just "member", because coordinator, tenant admin, super admin and god authority are all carried by separate yes/no flags instead. Any account set up that way would have been skipped: the fan-out would run, find nobody, quietly log "no active admins found" and stop — no email, no bell, silently, on every registration. No such account exists on the platform today, which is why nobody had hit it. The recipient list now matches how authority is really stored: the role words as before, or any of the four admin flags. Brokers and coordinators stay included even though the platform's main admin check deliberately excludes them, because they are the people who actually work the approval queue — the reasoning is written next to the code so neither half gets "tidied" away. Prevention: the query was lifted out into its own named piece so it can be tested against real accounts, and there are now 13 tests covering it — one per flag, one per role word, plus the exclusions that stop the wider net from spamming ordinary members, suspended accounts, or admins of a different community. This matters because the listener's existing tests could not have caught it: they need stand-in versions of the email and notification services, which made them order-dependent, so both were switched off long ago. The new tests need none of that. Confirmed properly rather than assumed — with the old one-line query restored, exactly the four flag tests fail and the other nine still pass.A member waiting for coordinator approval was told, in red, that their sign-in details were wrong. Reported by the same coordinator. Signing in before approval produced "Sign-in failed. Please check your details and try again." in a red alert box, with the actual reason — that a coordinator has yet to approve the account — demoted to small amber print underneath. The natural response is to assume you mistyped something and try again, forever. Because requiring approval is the default for a newly set-up community, this was not an edge case but the ordinary experience of joining. Waiting for approval is now presented as what it is: a calm green panel with a clock, headed "You're registered and waiting for approval", keeping the existing explanation of who approves and how long it usually takes, plus the link to contact the community. The misleading "check your details" line is not shown at all in this case. It is also announced to screen readers as a status rather than an error, so it does not sound like a failure either. A genuinely wrong password still produces the red alert, unchanged. Prevention: this page had no test coverage of sign-in outcomes whatsoever, which is why it went unnoticed; there are now three — one that the real reason leads and the misleading line is absent, one pinning the polite announcement at the markup level so a restyle cannot turn it back into an error, and one guarding the opposite direction, that a bad password still says so loudly. Verified by disabling the fix and watching the right two fail while the bad-password guard kept passing. The new wording is translated into all eleven languages by hand.
The sign-up form told people to enter "a valid international number" without saying how, and the tickbox outline was too faint to see even after it was enlarged. Two follow-ups to the sign-up fixes above. The phone field's guidance now explains the actual rule in plain words — start with
+and your country code, then drop the first0of your local number — both in the hint shown before anything goes wrong and in the message shown when the number is rejected. The coordinator had asked for it to say that UK numbers start+44and lose the leading zero; the wording deliberately states the principle instead of naming one country, because this platform serves timebanks worldwide and must not read as UK-only, and the example stays a neutral international one. Separately, enlarging the consent tickbox from 12 to 20 pixels turned out not to be the whole story: measured in a browser, its outline was drawn at about 1.2:1 contrast against the card, where the accessibility standard asks for 3:1 on the edge of a control you have to find and click. It is now about 3.95:1 in light mode with a matching treatment in dark. 🔴 Deliberately fixed on these two tickboxes only, not platform-wide. The faint outline comes from a shared value used by every input on the platform; raising it everywhere is a design decision for the owner, not something to slip into a bug fix, so the override is local and the code says to delete it if that global change ever happens. The same faintness therefore still affects every other checkbox in the app. Prevention: a test was added for something nothing had ever checked — that clicking the visible box toggles it. Every existing test in that file clicks the hidden native input, which works no matter how the visible parts are put together, so none of them could tell you whether a member can actually tick the box they can see. It passes.A coordinator could not find where to edit activity categories, because the page was filed under "Content" and the menu search could not match it. She reported searching both the admin panel and the broker panel and finding nothing. The page exists and always did, at
/admin/categories, and the categories themselves are genuinely per-community (each community owns its own rows, so renaming or adding one affects nobody else). Two things hid it. It sat under "Content", beside Blog posts, Pages, Landing page and Menus — a section that reads as website material, not as the categories a member picks when offering an hour. It now sits under Listings, where somebody would actually look. And it carried no search keywords at all, while the items around it did. The sidebar search matches an item's label, its section name and its keywords, so an item labelled "Categories" in a section called "Content" could only ever be found by searching the word "categories" — searching "activity" or "activities", which is what she and the system she is migrating from both call them, matched nothing. Measured against the real search function before and after: "activity", "activities", "activity categories" and "offer" all returned no match before and match now. Keywords were written by hand in all eleven languages rather than machine-translated, because they are exactly the short everyday words the automatic translator mangles. 🔴 Kept reachable rather than simply moved: these categories are not listings-only — the same table serves events, volunteering and resources — so a community with the listings module switched off keeps the item under Content instead of losing the page entirely. Also confirmed while checking: no community anywhere has an "Other" category, so her request for one is a single row to add rather than a feature to build.A member waiting for approval was left staring at a login form they cannot get past, with nowhere to go. The calm green "waiting for approval" panel added earlier explained the wait but offered only "Contact the community" — so the member's options were to email a coordinator or give up. On a community that approves members, and approval is the default for a new one, that dead end is the ordinary experience of joining. The coordinator who reported it put it exactly right: it would be better for them "to be able to see something, even the home page that they first saw". The panel now leads with Back to home alongside the contact link, so somebody who has just been told to wait can go and look around the community they have joined. 🔴 Honest about the size of this: the community home page is public and the login page has always carried a "Back to home" link at the very bottom, so this was never technically impossible — the link was page furniture, nowhere near the message, with nothing connecting it to "you are waiting". This makes the way out sit inside the panel that just told them they cannot come in. The same class of problem as the categories entry above: not a missing capability, a capability nobody could find. A test pins the link inside the panel specifically, and was confirmed to fail when the button is removed. 🔴 One part of her suggestion is deliberately NOT done: she also suggested pointing waiting members at the activity-preferences questionnaire to fill in meanwhile. That needs a signed-in session, which is precisely what a pending member cannot have, so it would mean letting unapproved accounts part-way in — a real change to who gets a session, not a wording tweak. Worth doing, worth designing properly, not worth slipping into a bug fix.
"Back to home" told ten languages to go back to their house. Found while wiring the link above. The phrase had been machine-translated as the physical dwelling rather than a website's home page in every non-English locale — German "Zurück nach Hause", French "Retour à la maison", Spanish "De vuelta a casa", Japanese "家に戻る", Arabic "العودة إلى المنزل" and the rest. It appears twice, once on the login page and once on the sign-up page, and both were live. All twenty values corrected by hand to the page sense ("Zurück zur Startseite", "Retour à l'accueil", "Volver a la página de inicio", "ホームページに戻る"). This is the same failure recorded when "Windows" became Fenêtres and "Mac" became Impermeable: short, common, context-dependent words are exactly what the automatic translator gets confidently wrong, and nothing in the pipeline can detect it because the key exists and is non-empty in every locale.
🔴 The new-member alert never said an approval was needed, and the admin panel never showed how many people were waiting. This is the real fault behind a coordinator reporting she got "no email, notification or badge alert" when somebody signed up — see the correction above for what it was wrongly blamed on first. The delivery records settled it: the email went out and was delivered within two seconds of the sign-up, and the bell notification was created and opened. Nothing was missing except the one fact that mattered. The alert read like routine news, not like a job. Its subject was "New member registered", its body said "log in to view their profile", and its button went to that member's profile — on a community that requires approval, where that person is locked out of their account until a coordinator acts. So she did what anyone would: ignored it as noise and went hunting in the admin panel instead. On a community that requires approval, the subject now says an approval is waiting, the wording says plainly that the member cannot use their account until you approve it, the email is styled as a warning rather than an informational notice, and the button goes to the pending-approvals list instead of the profile. Communities that do not require approval are untouched and still get the neutral wording — being told to approve somebody who needs no approval would be its own bug. Brokers and coordinators are deliberately routed somewhere different: they are refused
/admin/*by design, so linking them to the approvals queue would hand exactly the people who work that queue a dead link; they keep the broker members list, while still being told an approval is outstanding. The second half is the badge. The admin panel lists "Pending approvals" with no number beside it, and it turns out the reason is that three separate pieces existed and were never joined up: the sidebar's menu items have always supported a badge, the renderer that draws one has always been there, and a backend service that counts pending approvals had been written, unit-tested and registered — with no route, no controller and no caller anywhere. That service is now exposed and the sidebar reads it, so a waiting member shows as a count the moment a coordinator logs in, in the red "someone is stuck" tone rather than as decoration, refreshed each minute. 🔴 One trap fixed while wiring it: the counter counted members whose status was pending, while the screen it links to lists members who are not approved — two different columns that drift apart, and the same controller already carries a repair for approvals that leave the status stuck. A badge saying "1" that opens an empty screen is worse than no badge, so the two now use exactly the same test, with a test that fails if they diverge again. Prevention: the wording and routing decisions were lifted out of the send loop into a pure function so they can be asserted directly — the existing fan-out tests need stand-in mail and notification services, which made them order-dependent, and both have been switched off for a long time, which is precisely why this went unnoticed. Nine new tests cover the copy in each direction, the routing for admins, flag-only admins, brokers and coordinators, the count matching the list, and the count staying inside one community. A badge is never drawn for zero, and a failed fetch leaves the sidebar alone rather than showing a stale number.🔴 Event organisers on the new accessible website could not record anything about venue accessibility. The form for creating an event was missing all ten accessibility questions the old site asks: step-free access, an accessible toilet, a hearing loop, a quiet space, seating, accessible parking (and parking details), how to get there by public transport, who to contact for assistance on the day, and free-text notes. This is the exact information a disabled member needs in order to decide whether they can attend — missing from the website built for them. The questions existed on the form for editing a repeating event, so the platform could store the answers all along, and the part of the code that saves a new event was already passing them through; nothing was asking the organiser for them. The questions now appear on the create form, sharing one common block with the edit form so the two cannot drift apart again, and an organiser's answers survive a validation error instead of being wiped. Tested field by field.
Eight of the most-used pages kept their English name when a member chose another language. The page name is the browser tab title, the bookmark name, and what a screen reader announces when the page opens. On Feed, Listings, Events, Groups, Ideas and the three "create" forms it stayed in English on the new site while the old site translated it — confirmed by comparing all 41 core pages in Irish, then again in German, where every one now matches. The wording already existed in all eleven languages; the code simply was not using it. The "Federation" page was worse: it showed our own internal word for the feature instead of "Partner communities", untranslated. 🔴 Being honest about what is left: there are 253 hardcoded English page names in the new site's code, and only the nine proven to reach members are fixed. A new check records that number as a ceiling that can only go down, and fails the build if it rises, so the remaining debt is visible and cannot quietly grow. One file (the jobs section) holds 77 of them and is the obvious next job.
Four faults on the new accessible website, found by walking it side by side against the old one as two real members. Route coverage between the two has been complete for a while (707 of 707), but matching route lists is not the same as matching behaviour, and none of these four showed up as a failing test. 1) Members' messages were attributed to "Community member" instead of the person who sent them — the heading said "Conversation with E2E UserB" while every incoming message underneath was signed by a placeholder. The old site never had this problem because in a one-to-one conversation any message that is not yours is, by definition, from the other person; the new one was looking for a sender name that the message data does not carry. 2) The safeguarding notice was missing entirely. The old site tells members, on every conversation, that their messages may be read by a coordinator or by someone they have trusted to support them. The new site did not show this at all — the wording was translated and ready, but nothing displayed it, so members were not being told who can read their messages. It is now shown in the same place, with the same wording, and deliberately keeps the old site's fail-safe behaviour: if the information about who can see the conversation cannot be loaded, the notice is shown anyway rather than hidden. 3) "Browse organisations" could never work. It always showed "We could not load the organisations", for every signed-in member, because the request was being sent without proof of who was asking and the platform refused it. Two pages were affected. 4) The "Give feedback" link went to an email app instead of the community's own contact form. The old site deliberately sends people to the community's contact page, and only falls back to an email link on the platform-wide pages. On the new site every community got the email link — which is a dead end for anyone without an email app set up (disproportionately the people this website exists for), sent community feedback to the wrong inbox, and skipped the spam protection on the contact form. Each fix has a test that fails without it; two of the existing tests had been pinning the broken behaviour and were corrected.
The platform's own API was leaving the level name out of a member's progress, so no app except the old accessible website could show it. A member at level 3 is a "Contributor", and the old accessible site displayed "Level 3 (Contributor)" because it reads the progress information directly inside the platform. Everything that has to ask over the network — the new accessible site, the main app, the mobile app — only ever received the number, because the endpoint hand-builds its own copy of that information and had drifted from the original by omitting the name. The name is now included. The existing test for this endpoint only checked that it replied successfully, which is why the omission survived; it now checks the level name is actually there.
Changed
- The older accessible website is now frozen: we are no longer building on it, only reading it. Project NEXUS has two accessible websites — the original one built inside the PHP application, and the new one that took over
accessible.project-nexus.ieon 12 August. The owner decided on 13 August that all building effort now goes into the new one, and the older one is kept read-only as the reference for building it. It retires once the new one is judged finished. This is recorded in the three places a person or an AI agent would look: the project-wide agent guide, the takeover document that is the single place this status is stated, and a banner at the top of the older site's own developer guide. The distinction written down is deliberate and narrow: reading the old site is the point — it is the specification the new one is being built against — while writing to it stops, apart from a security fix, a fault making a live page unusable for a real member, or an unavoidable repo-wide sweep such as licence headers or translation-key parity. 🔴 Frozen does not mean switched off. The older site is still deployed and still serving real members on the community accessible domains and every/{community}/accessible/...address, so nothing was deleted, no routes were removed, and its tests and build stay in place. Retiring it remains a separate change with its own review, after a soak period. This reverses the direction of eight commits in the preceding week that added features to the old site; that work stays, but work of that shape does not start there again. - Corrected the readiness score for the new accessible website, which the takeover document had recorded as two different numbers at once. That page is meant to be the single place the status is stated, and it claimed 640 in one paragraph and 592 in its own summary table, while the document that actually owns the score said 651 out of 1000. All three numbers described the same measurement, so any reader would have quoted whichever they happened to read first. Both wrong figures now read 651, and the page carries a note telling readers to take the number from the owning document rather than restating it. 🔴 The automated check could not have caught this, and that is worth knowing: it compares specially-marked score values embedded as hidden comments, and both wrong numbers were ordinary prose with no marker. Two further stale claims on the same page were corrected at the same time — a section saying the new site's deployment files were "not on the server yet" and a table row saying its deployment path "is not built yet", both of which were overtaken when it was actually deployed and cut over on 12 August.
- The public repository is now called
Project-NEXUSinstead ofnexus-v1, and the documentation website has moved to its own permanent address. The old name read like a version number for a first attempt, which undersold a platform in production with real communities on it. The rename itself took seconds; the work was in the 115 places across 42 files that named the old one — including the licence attribution links the AGPL obliges us to show, the NOTICE file, the terms and About pages, the security contact file, the issue templates, and the documentation links the API hands to anyone integrating with us. GitHub forwards the old address permanently, so nothing broke at the moment of the change, and the production server keeps deploying normally because it follows that forwarding. Two traps were handled deliberately rather than discovered later. A check that runs on every push compares a link inside the changelog against a copy of the same link inside a script — changing either alone turns the build red, so both moved in one commit and the check was run to prove it. And the private backup repository is a separate repository that GitHub does not rename automatically; it was renamed to match and the restore instructions updated. Its name contains the old name as a prefix, so it was replaced first, before the shorter one — the other order silently mangles it. 🔴 The documentation website was the one thing that genuinely could not survive a rename. Its address was built from the repository name and GitHub does not forward the old one, so every existing link to it would have died silently. Rather than move it to a new address that would break again next time, it now has a permanent home atdocs.project-nexus.ie, and the deploy writes that address into the published site on every run — without that step GitHub quietly reverts to the old style of address. Verified rather than assumed: the version-consistency, documentation-hygiene and score-consistency checks all pass; the 23 frontend tests and 824 accessible-site tests covering the attribution links pass; and a search of the whole repository for the old name returns nothing except a single line of history. That one line was left alone on purpose: an entry from May records that a panel linked to an address which did not exist, and rewriting it would have described a broken link that was never broken. Links meant to be clicked were updated; a sentence describing what an address was at the time was not. 🔴 The attribution link inside the live app still shows the old address until the next deploy, because it is baked in when the app is built. It forwards correctly meanwhile, so members are unaffected. The local working folder was deliberately left unchanged — it is not the repository name, and renaming it would break the container setup. - The community logo in the footer is now close to the size it is in the header, instead of being shrunk to the point of illegibility. The footer used the smallest of the three logo size presets, which capped a wide logo at 28 pixels tall and 150 wide — enough for a plain wordmark, but a lockup like Minehead and Coast Time Bank's (a roundel beside two lines of text) became unreadable at that scale. The preset now allows 40 pixels tall and 220 wide for wide logos, with proportional increases for landscape and square/stacked shapes, so the footer mark carries roughly the same visual weight as the header's. The aspect-ratio-aware sizing is unchanged: a narrow or stacked logo still gets more height than a long wordmark, so no shape ends up squashed. Only the footer uses this preset — the header and mobile drawer use the larger ones and are untouched. The reserved intrinsic dimensions were raised to match, so the space kept for the logo before it loads still cannot be too small (which would make the page jump as it appears). Verified in a browser against the real tenant logo: the footer mark measures 40 pixels tall against the header's 44, where it was previously 28.
Fixed
The small coloured badge above a page title ("Community exchange board" on Listings) was breaking onto two lines, with its little icon stranded above the words instead of beside them. Reported by the owner on
hour-timebank.ie/listings. The badge is shared, so the same fault appeared above the title on Listings, Volunteering, Blog, Search, Resources, Groups, Members, Marketplace, Organisations, Events and the Help Centre. Root cause: the styling framework we use resets every icon to behave like a paragraph, which starts a new line. The badge component is built to take an icon in its own slot, sitting beside the text; here the icon had been written inside the text instead, so the reset applied and pushed the words down. Measured on the running site: the badge was 34 pixels tall where it should be 20. It is now 28 pixels tall on one line, with the icon and the text centred against each other. The same mistake was found in 34 other badges across the platform — legal pages, achievements, skills, leaderboard, job listings, marketplace seller badges, the broker tools and parts of the admin panel — and a further 13 where someone had patched around it by hand, one badge at a time. Rather than repeat that patch 34 more times, one styling rule now keeps any badge with an icon on a single centred line, and the 13 hand-patches were removed so every badge is spaced the same. The rule is deliberately written to touch only badges that contain an icon, so the several hundred text-only badges are unaffected. Prevention: a test on the shared page-title badge fails if the icon is ever moved back inside the text. Checked in a real browser on two live pages, one for each of the two fixes, and the icon and text are confirmed to share the same centre line. Typecheck and the tests for every affected page pass; the three failures seen alongside them are pre-existing and already on the project's known-failing list, or are a known intermittent unrelated to this change.The Explore page printed the raw text
achievements.xp_valueunder every member in "Top Contributors", instead of their XP total. Spotted live onhour-timebank.ie/explore. Translated text is stored in files grouped by area ("namespaces"), and to keep the app starting quickly only four of them are loaded up front — every other page asks for the ones it needs when it loads. The Explore page asked for its own file but not the gamification one, then tried to use a gamification phrase for the XP figure. When a phrase's file has not been loaded, the app has nothing to show and falls back to printing the phrase's internal name, which is what members saw. The page now asks for both files, so the figure renders as "1,234 XP" as intended, in every language. The same mistake was found in three other places and fixed at the same time: the Caring Community home page's "change your answer" link, the support-relationships page (pause / end / resume buttons, the pause dialog and its three confirmation messages), and the caring-community onboarding dialog's title and introduction — all four would have shown internal names to members. Prevention: a new check,react-frontend/scripts/check-i18n-namespaces.mjs, refuses the build when a file uses a phrase from a translation file it never asked for. It was confirmed to fail on the original Explore page fault and to pass once fixed, and it reads the list of always-loaded files fromsrc/i18n.tsrather than repeating it, so the two cannot drift apart. It runs as part ofnpm run build. Verified in a real browser rather than by reasoning: with the gamification file unloaded the phrase does not resolve, and after loading it the same call returns "1,234 XP".The documentation website's "Accessible Frontend" section described a version of the platform we stopped serving the day before, and did not mention the site that replaced it. Anyone landing on that section of
docs.project-nexus.iesaw two GOV.UK research documents and nothing else — no mention that there are now two accessible frontends, no mention of which one answers which addresses, and no link to the page that actually records the changeover. That page was listed, but buried in a different table further up, so the reader most likely to need it was the least likely to find it. Worse, the architecture document it did link still described the accessible frontend as "an isolated, HTML-first Laravel frontend" and had not been reviewed since 14 July — weeks before the Node application took over the main accessible address. So the only architecture description on the documentation site was of the frontend that is retiring, presented as though it were current. Three corrections. The section now opens by saying plainly that two accessible frontends are live, which serves what since 2026-08-12, that/versionis the only way to tell them apart, and that the changeover page is the one place status is stated — with that page listed first in the section rather than buried. The architecture document now opens with the same warning, keeps its GOV.UK rules (branding prohibitions, approved packages, HTML-first) which still bind both frontends, and has its architecture section retitled so it cannot be mistaken for a description of what serves the site today. And the monorepo boundaries document, which still said the Node site was "not deployed" and that its deployment isolation stood "until a deployment path is deliberately built", now records that it was deployed and cut over, that it is a production track deployed from this repository, and that the isolation rules it describes apply to the second backend rather than to it. Documentation hygiene, score-consistency and lint checks all pass.The phone app was missing the source-code link the AGPL licence requires, and
mobile/README.mdclaimed it was already there. The app did show a licence line — "Project NEXUS is open-source software licensed under AGPL-3.0-or-later.", translated into all seven of its languages — at the foot of the Profile hub and five other screens. But that line was the whole notice: there was no link to the source code and no copyright notice anywhere in the app, and a repo-wide search confirmed the repository URL appeared nowhere undermobile/app/**,mobile/components/**,mobile/locales/**ormobile/native-locales/**. AGPL-3.0-or-later Section 7(b), and theNOTICEfile's canonical wording, require an interactive interface to carry both the attribution notice and a source-code link.mobile/README.mdstated the notice "is displayed in the Profile/About screen" and "must include a copyright notice and visible source repository link" — the first half was half-true, the second half was not true at all, and the app has no About screen. What changed: a newmobile/components/SourceRepositoryLink.tsxis now the single definition of the notice and of the repository URL. It renders the licence line, a copyright notice (Copyright © 2024–<year> Jasper Ford, with the year interpolated rather than baked in), and a tappable link to https://github.com/jasperfordesq-ai/Project-NEXUS — matching what the three web clients already do (react-frontend/src/components/layout/SourceRepositoryLink.tsx,accessible-frontend/views/layout.blade.php,web-uk/src/views/partials/footer.njk). It replaces the six copies of the old bare licence line, so the notice can no longer drift between screens. All new wording goes through thecommonnamespace in all seven mobile locales (en, ga, de, fr, it, es, pt); the URL itself is never translated. The link has a screen-reader label and a 44 px minimum tap target.mobile/README.mdnow describes where the notice actually lives instead of asserting a compliance claim that was not met. Prevention:mobile/components/SourceRepositoryLink.test.tsxfails if the licence line, the copyright notice, the "Built on Project NEXUS by Jasper Ford" wording, the accessibility label or the link disappear, if the link stops opening the canonical URL, if any locale is missing a key, or if the URL is ever hardcoded into a translation string;mobile/app/(tabs)/profile.test.tsxadditionally asserts the link is present on the Profile hub itself.The community home page on the accessible site could hang forever instead of showing an error, and nobody would have been told. This is the busiest page on the new accessible site, which took over
accessible.project-nexus.ieon 12 August. If the platform's API answered that page with a server error, the page did not fail — it stopped. No error page, no "try again", nothing: the visitor's browser simply spun until it gave up. And it was silent on our side too, so this could have been happening without appearing anywhere we look. The cause is a difference between two halves of the same piece of code. That page handles two cases: the "choose a community" list on the shared address, and a single community's own home page. The first half was carefully written to catch failures and show a proper page. The second half was not. The routine it calls deliberately tolerates the API being unreachable — it shows the page without the statistics band — but passes every other kind of failure back up to be dealt with, and nothing was there to deal with it. Because of how the web framework works, a failure at that point means the reply is never sent at all, and our crash reporter only sees failures that reach the end of the chain, which this one never entered. The second half now catches failures the same way the first half does: a signed-out visitor is sent to sign in, a missing community gets the not-found page, and a genuine server fault gets the error page and is reported to the crash reporter. Verified by reproducing it, not by reasoning about it. A test was written that makes the API return a server error and then asks for a community home page. Against the old code the request timed out with no reply, and the site's own request log recorded no result code at all — which is the fault's signature. Against the fixed code it returns a proper error page. That test is now part of the suite, and it was confirmed to fail when the fix is removed, so it genuinely guards this and cannot pass by accident. The full accessible-site suite — 73 files, 2,123 tests — passes, along with the code-style and branding checks. Found during an audit of the accessible site, which also confirmed the good news: all 707 pages the old accessible site has are present on the new one, with none missing, checked twice by two different methods.Three checks on the accessible site were being trusted but not actually run, and the copyright notice was missing from 68 of its files. All three came out of the same audit as the home-page fault above, and all three are the same shape: something everyone believed was protecting us was only happening when a person remembered to do it by hand. The page-coverage check had quietly gone out of date. The accessible site keeps a generated list comparing every page the old site has against every page the new one has, and the documentation calls it a "live alarm" for a page that was never rebuilt. It was not live — nothing regenerated it — so it was only ever as current as the last time somebody ran it manually, and it had already drifted: it recorded 721 addresses where the code had 722. That particular gap was harmless (the extra one was an internal address used to check which site answered), but a genuinely missing page would have gone unreported in exactly the same way. The site's automated checks now regenerate the list on every change and fail if it no longer matches what was recorded, and separately fail outright if any page of the old site has no counterpart. 🔴 This is deliberately not a straight file comparison: the generated files stamp the date, the code version and the folder they were made in, all of which differ on the build machine, so comparing the files themselves would have failed every single time and been switched off within a week. It compares the counts, which are the part that means something. The accessibility checks were not running either — 24 of them, on a site whose entire reason for existing is accessibility. They now run on every change. They use the self-contained version that starts its own stand-in for the platform and refuses anything other than read requests, so nothing touches real member data; the fuller version, which signs in for real, is deliberately excluded. When they fail, the report and screenshots are now saved so the failure can actually be diagnosed instead of being just a red cross. And the licence headers were missing from 68 files. Every source file is required to carry the AGPL copyright notice, and the checker that enforces that only looked at PHP and TypeScript — so the whole accessible site, which is JavaScript and templates, sat outside it. 5 code files and 63 of its 335 templates had no notice at all. All 68 now do, the checker covers them, and the tool the guide tells people to run to fix such failures was taught the template comment style, so the documented fix now actually works. Verified rather than assumed, in the way this repository has learned to insist on: each new check was confirmed to fail when the thing it guards is broken — a licence header was removed and the gate went red, then passed again when restored — because a check that cannot fail is worse than no check, having previously hidden a real defect here. The full accessible-site suite (73 files, 2,123 tests), the accessibility checks (24), branding, code style and stylesheet build all pass after the change. 🔴 One count moved and it is worth knowing why: the internal
/versionaddress is now classified as machinery, alongside the health check, rather than as "a page the new site has that the old one doesn't". It is what the deploy check and the changeover check match on, not something a member can visit, so counting it as a page was miscounting the one number that list exists to keep honest.The front page of the public repository advertised three technologies out of a stack that has about fifteen, and described the accessible site as software we stopped serving it with the day before. The row of small badges at the top of
README.md— the first thing anyone evaluating the project sees — listed only PHP, Laravel and React. Missing from it: TypeScript, HeroUI, Tailwind and Vite (the web app); Node 22, Express, Nunjucks and GOV.UK Frontend (the accessible site, which is a separate application, not a template folder); Expo and React Native (the mobile app, its own codebase); .NET 10, PostgreSQL and RabbitMQ (the second backend); and MariaDB, Redis and Meilisearch, which every install depends on. The badges are now grouped under headings — Backend, Web app, Accessible site, Mobile, Second backend — so the shape of the platform is legible at a glance instead of being understated to a third of its real size. The more serious half of this is accuracy, not completeness. The stack table said the accessible site ataccessible.project-nexus.iewas rendered by Laravel Blade, and that the Node application was "retired 2026-08-10, container stopped" and "never deployable from this repository". All three statements were true when written and all three were wrong by the time anyone read them: the Node application took that address over on 2026-08-12, it is deployed from this repository, and every deploy from now on must carry the--with-webukflag or it refuses to run. Checked against the live site rather than the documentation — askingaccessible.project-nexus.iewhich application answered returned the Node one. The tables, the architecture diagram and the repository map now say that two accessible frontends are live at once, which one answers which addresses, that/versionis the only way to tell them apart, and that the changeover is unfinished. Two whole directories were absent from the repository map:mobile/, the Expo / React Native app, andcontracts/events/v2/, the shared event contracts both backends have to satisfy. Both are now listed. And two claims about the mobile app were corrected to match the audit already recorded above — it is a separate codebase rather than a wrapper around the website, only the Android release path is complete, and neither store version is published. The retired web-wrapper wrapper is named once, as history, with the note that its project directory is not in this repository. Docs hygiene, version consistency and markdown lint all pass; the diagram was rewritten to avoid two constructs that could have failed to draw on the repository home page. The two secondary tracks were also described in a way that undersold them, and that has been corrected on the owner's steer. Calling the ASP.NET backend "secondary, kept for contract comparison" reads as a spike someone abandoned. It is 254 controllers, 165 migrations and 3,386 tests standing at 712/1000 against a fixed rubric whose single question is whether it is externally contract-identical to Laravel — substantially built, paused rather than abandoned, with a finite ordered queue remaining. The section is now headed "The second backend" and states the actual point of it: the clients are built to switch backends by configuration (VITE_BACKEND_TARGET=laravel|dotnet, with backend differences confined to adapter modules), the goal being two frontends by two backends where neither frontend changes behaviour when its backend changes. The README also says plainly what remains true — not certified, not the production default, not deployable from here — because both facts matter at once. The score is quoted with a pointer to the one canonical document that owns it, and with the warning that two separate 1,000-point rubrics exist and must never be added together. And the accessible site is now described as a full application rather than a template folder: its own HTTP server, routing, middleware, session store, view layer, asset pipeline, brand checks, 74 test files and production container, talking to the platform over the same public API the React app uses — which is precisely why it, too, can be pointed at either backend. The badge headings said the wrong thing about which track is being worked on, and the takeover document had gone stale. "Second backend (contract comparison, service retired)" was both dismissive and misleading: the enforced status markers show the two workstreams were paused together on 2026-07-15 and have since diverged — the accessible frontend's pause was lifted on 2026-08-11 and it went live the next day, while the second backend is still paused. The headings now read "Accessible site (the second frontend — live, in active development)" and "Second backend (switchable, substantially built)", and the README says in words that one is being built out and the other is not, so neither status can be read off the other. 🔴 And the document the README points at for that status still said the Node site was "not yet deployed" and that "nothing has been deployed to the server" — written the day before the cutover and not revised after it. Since it declares itself the single place the changeover status is stated, that made it the most misleading page in the repository. It now records the cutover, that both accessible frontends serve live traffic, that/versionis the only way to tell which answered, and that the phase turns on Blade being switched off rather than on the first address moving — so this is still Phase A. The GitHub repository description was updated to match the README for the same reason: it also said "Laravel 12 + React 19" and nothing else.Two live web addresses were never having their cache cleared after a deploy, so they could keep serving the old site indefinitely. After every deploy we tell Cloudflare — the service that keeps copies of our pages around the world to make them fast — to throw away its stored copies, so visitors get the new version. That step was working, and it reported "all 7 domains purged successfully" every time. The problem was the number 7. The list of addresses was typed into the script by hand, and the account actually has nine. The two that were missing are not spare or parked addresses:
timebanks.usserves the main platform app, andpairc-goodman.comserves the app and the API. Both are set up on the production server and both answer normally. So for as long as that list has been out of date, anyone visiting those two addresses could have been served an old copy of the site, with no expiry we control, while the deploy log cheerfully reported complete success. The fix is not just adding the two. Adding them by hand would leave the same trap for the next address anyone adds, so the script now asks Cloudflare which addresses exist and clears all of them, meaning a new one is covered automatically with no code change. The hand-written list is kept underneath as a floor, so if Cloudflare cannot be reached, or the access key is not permitted to list addresses, everything we already knew about is still cleared — and in that case the script now says loudly that its coverage may be incomplete, instead of quietly doing less and still printing success. It also announces any address it finds that was not on the list, so the two worlds cannot drift apart unnoticed again, and there is a new--dry-runoption to show what would be cleared without clearing anything. Verified against the real account, not assumed: the new script was run in dry-run mode on the production server using the live access key, and it discovered and listed all nine addresses including the two that were missing. 🔴 This needs a deploy to take effect — the deploy runs the copy of the script that is on the server, so until the next deploy the old seven-address version is still what runs. 🔴 A second, unrelated fault was found onpairc-goodman.comwhile checking this and is deliberately NOT fixed here, because it is a server configuration change rather than a code change: that address is wired to fixed port numbers belonging to one of the two halves of our zero-downtime deploy setup, instead of following whichever half is currently live. It is therefore serving the previous release right now — confirmed by reading the build identifier it returns, which is the older one — and it will keep doing so. Worse, the next deploy rebuilds that half, so that address can be expected to break for the duration of the build. It needs the same follow-the-active-half setting the other addresses use, which requires an authorised change on the server.Installing the app from
app.project-nexus.ieproduced an app that only covered the admin panel — now fixed, and pinned by tests. This is the defect recorded but deliberately left alone in the "Get the app" work above. When a phone or computer installs the app, it asks us for a small settings file that says which address the app should open at and which pages count as "inside" the app. On the main platform address that file said the app should open at/admin/and that only pages beneath/admin/were inside it. So an installed app opened the admin panel, and every ordinary page — dashboard, listings, messages, wallet — was treated as an outside website and opened in the browser instead, losing the installed app's appearance and behaviour. The cause was a slug being used as a path. The platform's own master community is stored with the nameadminin the place a community's web-address segment normally goes, and the code that works out the prefix only returns "no prefix" for communities that have their own web address. The platform host has no such community, so it fell through and usedadminas if it were a path. The prefix is now worked out inside the install-file code itself, where the platform host is explicitly treated as covering the whole site, and a community with its own web address still covers the whole of that address. The same fault had a second route in and that is closed too: requesting the install file while sitting on an admin page (/admin/dashboard) matched the master community by name and reproduced the identical/admin/result — the master community is now never matched that way, because its stored name is a platform address, not a community's. Community web addresses were correct throughout and are unchanged. Verified rather than assumed: two new tests cover the platform host at the root and on an admin page, alongside the existing tests for a community reached by path and a community on its own web address. All five pass — and the two new ones were confirmed to fail with the old behaviour put back, reporting/admin/where/is expected, so they genuinely catch this and not something adjacent.The Features page described our native mobile app as a piece of software we do not use, and overstated how far along it is. The public Features page said the mobile app was built with retired web-wrapper, a tool that wraps the website up as an app. That is not what our native app is: the real one is a separate app written with Expo and React Native, living in its own part of the codebase and talking to the same API the website does. The retired web-wrapper project is not even present in this repository any more — it was removed in March and is deliberately excluded. So the page was describing software that is not here, to anyone reading it to decide whether to work with us. The technology line now names Expo and React Native, and the module description says plainly that it is a separate app sharing one set of code for both Android and iPhone. The honesty about release state matters as much as the correction. The old wording said native builds were "tested but not yet under continuous release", which is vaguer than what we actually know. It now says neither store version is published yet, that Android is closest because its signing, build settings and notifications are all in place, and that the iPhone version additionally needs an Apple developer account and Apple's review before it can be published — the same account of the position given on the "Get the app" page, so the two pages no longer contradict each other. All three pieces of wording were translated into the other ten languages. 🔴 Two brand names were mangled by the machine translator and corrected by hand — the same class of problem as the "Windows"/"Mac" mistranslations recorded above: Irish turned "React Native" into "React Dúchasach" (translating "native" as an ordinary adjective), and Arabic translated "App Store" into ordinary words. Every language was then checked for all five brand names, and all eleven now keep them intact. Also found and not changed here: the admin-facing white-label sales copy still describes an "iOS and Android retired web-wrapper app", which needs the same decision about retired web-wrapper's future that the "Get the app" page is waiting on.
The "provided by" logo in the footer was blown up far bigger than the picture itself, which is why it looked blurry. The footer only limited how tall these logos could be, never how wide. A tall, squarish logo is fine with that — it hits the height limit and stops. But a long, thin wordmark like Timebanking UK's has to get very wide before it gets tall, so it stretched to about 470 pixels across, which is wider than the uploaded picture actually is. Stretching a picture past its own size is exactly what makes it look pixelated, so the fault and the blurriness were the same thing. There is now a limit on the width as well, and the two work together automatically: a square logo is still stopped by the height and gets a generous 112 pixels of room, while a wide one is stopped by the width instead. Measured on the running page rather than assumed — a square logo comes out 112×112, the long Timebanking UK shape comes out 272×64 instead of 470×110, and neither is stretched beyond the uploaded picture any more. The community partner logo in the neighbouring column had the identical fault and would have blown up the same way the moment a community uploaded a wide logo; it is now limited to the same size as the dashed placeholder box that sits there beforehand, so the footer no longer jumps about when a real logo replaces it. No uploads need redoing.
The accessible site is now live on its own web address, and watching it for ten minutes found two things worth having.
accessible.project-nexus.iewas switched from the old accessible site to the new one, with the community web addresses deliberately left alone. Twelve checks were run straight afterwards and all passed, including the two faults fixed beforehand, old bookmarked addresses, and proof the rest of the platform was untouched. 🔴 Then the new site's own log showed an error I had introduced the same day. Every visit to an old bookmarked address raised an application error. The redirect itself still worked — anyone with an old bookmark landed in the right place — but the code carried on running after the reply had already been sent, and then failed trying to add a security header to it. The cause is exactly the mistake I had found and fixed a few hours earlier in the neighbouring piece of code, and then made again here: "no answer yet" and "already answered" both look like nothing, so treating nothing as "this community does not exist" ran the not-found path on a request that was already finished. Fixed, with three checks that fail if it comes back — and confirmed by putting the fault back and watching them fail. 🔴 And the safeguard that stops a routine deploy quietly undoing the switch had not actually switched itself on. It is designed to record "the new site is live" the first time it sees the new site answering on a real web address — but that check runs during a deploy, and the switch happens afterwards, by hand. So it looked, correctly saw the old site, correctly declined to record anything, and left the protection dormant for exactly the window in which an ordinary deploy would have reverted the switch with nothing failing. There is now a command to switch the protection on at the moment the switch is made, it can only do so if the new site genuinely answers, and it has been run — so the protection is armed. Also recorded but deliberately not fixed: the "report a problem" link in the footer adds the current address to itself, so on the contact page each click makes the address longer, without limit. That is not new and not caused by the switch — the old accessible site does exactly the same, which is why the new one copied it. It is logged as its own piece of work.The browser tests were checking the "create a listing" page against a page that does not exist — 23 times. Same wrong address as the two buttons below, and the same reason nobody noticed: it does not produce an error, it produces "Listing Not Found". So these tests spent their whole life filling in a form on a not-found page. Correcting the address was only half the job, and this is the part worth recording. Most of them were written as "if the field is there, check it" — so on the not-found page there were no fields, nothing was checked, and they passed. Pointing them at the real page exposed the second fault: they looked for fields by an internal name (
name="title") that this platform never produces, because the component library generates those names automatically. Fields have to be found by their visible label instead — the same conclusion reached last week when six other tests were repaired. So the tests were wrong twice over, and either fault alone would have hidden the other. Every selector was read off the running page rather than guessed. The identical fault in the same file for events was fixed too — five tests pointed at a create-an-event page that likewise does not exist. 🔴 Two traps found while doing it. A test that navigates and checks immediately fails against a page that is merely still loading: the app briefly shows "Checking authentication...", which outlasts the tests' default five-second patience, and the resulting "element not found" reads exactly like a broken feature. There is now one shared step that waits properly. And the category chooser defeated three reasonable-looking selectors in a row — the obvious one matches an invisible button one pixel wide, which the test tool quite correctly refuses to click. All 31 tests in the two files now pass, verified by running them.The category chooser can be driven after all, and that unblocked the three most valuable tests. Creating, editing and deleting a listing all need a category, and the control had defeated every attempt — clicking an option left it unselected with the menu stuck open, which then made the next click fail in a way that looked like a broken submit button. Reading the component rather than guessing at it gave the answer: the menu contains a search box, and that is where the keyboard focus goes, so the list has to be typed into before a choice can be made. It was easy to miss because that box is not a plain text field and does not show up as one. Those three tests are now switched on and passing, which means creating, editing and deleting a listing are covered end to end for the first time.
A third wrong address was found in the same file: the edit page is reached one way round and the test asked for the other, so it sat waiting for a form that was never going to appear.
The four feed tests were checking for something the app does not have. They looked for a typing box on the feed page. There isn't one — the feed's "What's on your mind?" is a button that opens a window with tabs, and the writing area inside it is a rich-text editor, not an ordinary text box. All four have been rewritten against what is really there and pass. One of them had also been checking for success in a way that a failure could satisfy, the same false-pass shape removed from the shared list last week; it now waits for the window to close, which is the app's own signal that the post was accepted.
Two more tests that had been failing for a while are fixed: one was looking for a page heading before the page had finished loading, the other allowed only one heading where the page legitimately has two.
The stale check in the Future Care Fund page's own tests is fixed too — it looked for the word "loading" on screen when the page actually says "Calculating your fund...", and had been failing quietly since the tests began using the real English wording.
🔴 One honest caveat. Run in parallel against a local development server, two of the event tests can still fail — the page's code is fetched on demand and, when several browsers ask at once on a cold server, it arrives too late and an error page shows instead. Run one at a time, all 31 pass. This does not apply to the real build, where nothing is compiled on demand. Test data created during verification was removed afterwards and the local database confirmed back to its exact starting state.
Two "create a listing" buttons led to a dead end that looked like a missing listing. Both pointed at an address the app does not have — the create page lives at
/listings/create, and these two asked for/listings/new. The reason nobody noticed is the interesting part: it did not produce a "page not found". The address for viewing a single listing is/listings/followed by that listing's reference, so/listings/newwas read as a request to view a listing whose reference is literally the word "new". There is no such listing, so the member got "Listing Not Found — Listing not found or has been removed". Anyone who clicked it would reasonably conclude something had been deleted, rather than that the button was wrong. The two were the "Create Listing" button shown on the Discover page when a community has no listings yet — so it greeted exactly the members a new community most needs to encourage — and the "Offer help now" button on the Future Care Fund page. Every other create-listing button in the app already used the correct address; these two had been missed. Confirmed by using the pages in a browser rather than by reading the code: before the fix the old address really did produce the not-found page, and after it both buttons land on the working "Create New Listing" form. Two tests now hold each button to the correct address, and each was checked by putting the fault back and confirming the test failed. Found while repairing the browser tests' shared lookup list, which contained a dead entry pointing at the same wrong address.Audited the accessible-site switchover before doing it, and found two faults that would have gone live. Both were the same kind: the new site answering "yes, here is a page" where the old one correctly says "that does not exist". 🔴 A mistyped community name showed a full page instead of "not found". The old site returns not-found; the new one returned a complete 24 KB page. No other community's information was exposed — it showed generic platform content — but somebody who mistyped an address was told nothing was wrong, and every misspelling became a page search engines could index. 🔴 Every page was also reachable without naming a community at all, and in that state every one of the community's on/off switches was ignored. The platform deliberately refuses those addresses; the new site served them. Nothing was exposed in the live setup, because it happens to have no default community configured — but the local development setup does set one, so any deployment inheriting that would have served one community's information on the shared address. Both fixed, with fourteen new checks covering them, including the case that must not change: when the platform is unreachable the site must not tell members their community does not exist, because that is a false statement about their own data and worse than a thin page. Also confirmed working, rather than assumed: the accessibility links in the main app point at addresses the new site genuinely serves; communities with their own web address are untouched by this change; old bookmarked addresses still redirect correctly; and the terms-acceptance screen keeps the community in the address so members are not thrown out of their own community. And three of my own mistakes, caught by running everything rather than only my new checks: I hand-wrote a "not found" page when the site already had a proper one; I misread a success signal as a failure and started refusing requests that had worked perfectly; and I changed behaviour for every web address when only one was actually wrong. The fix now applies only to the address being switched.
The browser tests' shared list of "how to find things on the page" was mostly fiction, and one entry was quietly reporting success when things had failed. Yesterday's repair fixed the two navigation entries and deliberately stopped there. Checking the rest against the real running site found that nine of them matched nothing at all — they described buttons, lists and pages that do not exist in the shape written down. A test using one of these does not report "this selector is wrong"; it reports that the feature is missing, which is the most expensive kind of wrong answer because it sends someone to investigate a healthy feature. One was worse than useless. The entry for the little pop-up confirmation messages was written loosely enough that it also matched ordinary red error boxes — and the app uses those in hundreds of places. So a test that finished an action and then checked "did a confirmation appear?" could be satisfied by the error message explaining that the action had failed. That is a test that passes when the product is broken, and there were several of them. It has been narrowed so it can only match a real confirmation message. Each entry was checked against the live site rather than reasoned about, and each repair was then confirmed by deliberately removing the thing it looks for and checking that the right test — and only the right test — failed as a result. Two further details worth recording. The list of listings and the "still loading" placeholder are built from identical styling, so anything that recognised one recognised the other; the repair is attached to the results themselves, so it can no longer mistake an empty loading page for a page of results. And the message box was being found by the English words in its placeholder text, so it worked in English and nothing else — the platform has eleven languages. One entry was deleted rather than repaired: it looked for a link to the dashboard, and there is no such link anywhere on the site at any screen size — the dashboard is reached by a button. Nothing was using it. 🔴 A separate fault was found while checking this work, and fixed. The shared test login was reading a setting name that nothing anywhere sets — not the test setup, not any of the six places the build system supplies it. It therefore always fell back to a made-up account that exists on no environment, so every test that signs in this way failed at the sign-in step, having never reached a single check.
messages.spec.tswent from 0 of 8 passing to 7 of 8 on that one change alone. The remaining failure is a genuinely separate timing weakness in that test and is not fixed. Nothing user-facing changes: the app changes are eight invisible markers added so tests have something stable to hold on to.