TBC App - Community App (Dev Bundle)

SKU: TBC-APP-DEV-BUN

The full React Native source plus the build dashboard. Configure your site, drop in your assets, and ship to both stores yourself — no coding required.

Price range: $50.00 to $500.00
$50.00 per year + $399.00 one-time Lifetime license

v1.3.27
Changelog · v1.3.27

1.3.27 — 2026-10-19

📱 What's New — store release notes

  • If you joined in the app and pay for your membership on the website, the app now lets you straight in.

🛠 Fixed

  • The renewal screen closes as soon as a membership opens again. When a member's access came back while the app was open (a purchase on your website, or an admin), the renewal screen stayed up until the app was restarted. It now closes the next time the app reads its settings.

🖥 Dashboard & builds — if you build your own app

Ship: OTA-safe — JS-only, push from the dashboard's Live updates page.

📡 OTA & Internal

  • The in-app purchases guide explains how members who pay on your website get in, which tags and roles count, and what to keep out of Fluent Community Pro's tag map.

1.3.26 — 2026-10-19

📱 What's New — store release notes

  • Behind-the-scenes improvements and fixes.

🖥 Dashboard & builds — if you build your own app

Ship: Needs a new build — native changes; build from the dashboard's Build page.

  • Android store builds finish on EAS's default build machine again. Android release builds compile Kotlin inside the Gradle daemon, so a store build fits EAS's default 16 GB build machine. 1.3.25 gave the Kotlin daemon its own 2 GB heap instead, and on SDK 58 a production build could still run out of memory and stop with "Gradle build daemon disappeared unexpectedly", because that second JVM held its memory beside Gradle's while the native libraries compiled. plugins/withAndroidBuildMemory.js now sets kotlin.compiler.execution.strategy=in-process in place of the Kotlin daemon's heap cap; the two-worker limit and the CMake job limits stay as they were. It applies to every build profile; iOS builds are unchanged.

📡 OTA & Internal

  • The composer and the launcher read a screen transition's data the same guarded way AppWebView does, so a transition that carries no data (the tab bar's) no longer logs a "Cannot read property 'closing' of undefined" error.

1.3.25 — 2026-10-14

📱 What's New — store release notes

  • Arrange your home screen: press and hold to reorder or hide cards, where your community allows it.
  • The app is quicker and uses less data.
  • A notification email's link marks it read in the app.
  • Screen readers name header buttons, switches and profile fields.
  • Community menu tiles show each space's picture.
  • On a tablet, sign-in looks like your website; on iPad the tab bar fits Split View.
  • The bell's count and each space's badge stay right.
  • The end of a page clears the phone's navigation bar.

✨ New

  • Members can arrange and hide the widgets on a screen you open up for them. Widget screens stay locked by default, which suits a menu-like page that should look the same for everyone. For a screen that works as a home page, Customize › Widget Screens has two switches on each screen, "Members can rearrange widgets on this screen" and "Members can hide widgets on this screen": turn on either or both. A member then presses and holds anywhere on that screen to open a map of it; with VoiceOver or TalkBack on, a screen where members can hide widgets ends with an Arrange widgets button that opens it, in English, Spanish, Greek and Dutch. The screen stays behind the map, blurred, and each widget is an icon on it, in the same columns the screen has. With rearranging on, the icons wiggle once and the member drags them into a new order; the screen behind moves as each one drops. With hiding on, each icon has an eye. Tapping it takes the widget off the screen and into a Hidden list, opened from the Hidden button beside Done, and tapping it there puts it back where it was. Pinned widgets stay first and can't be moved or hidden. Each member's arrangement is saved on their own device for that screen. Turning a switch off keeps it without using it, so turning it back on restores it. Every screen starts locked after the update, and an order a member saved before comes back when you turn rearranging on. The switches come with the companion plugin's update.
  • The cart names each pricing rule and shows the price before the discount. On a FluentCart store using FluentCart Pro's Dynamic Pricing, the cart lists each rule that took money off under its own name, a shipping rule too, in green above the Discount row, as the checkout page does. The Discount row keeps what coupons take. A cart line made cheaper by a coupon or a rule shows its original unit price struck through beside what one costs now, and screen readers say "Original price" before it, in English, Spanish, Greek and Dutch. It comes with the companion plugin's update; with an older plugin, and on WooCommerce stores, the cart looks as before.
  • A My Courses menu item. It opens the Courses screen on My Courses, so an app can offer the course catalog and a member's own courses as two entries, for example My Courses in the launcher and Courses in the tab bar. Add it from the Menus picker in Customize; it is never placed for you, and it shows only where Courses does. In the tab bar it opens as a tab, the way Courses does. Its label is the screen's own "My Courses", in English, Spanish, Greek and Dutch.
  • A member your site has blocked can sign in and subscribe their way back in, as on your website. A site that blocks lapsed members through FluentCRM lets them sign in on the web to an "Access denied" page, but the app refused them at sign-in. Where your site sells its membership in the app, a blocked member now signs in to an Access denied screen in your site's words, with Subscribe, Restore purchases and Sign out; a member blocked while signed in moves to the same screen instead of being signed out. Once their purchase goes through and your site lifts the block, the app opens the community in place, with no restart. Until then the screen is all they can reach, and they get no push notifications. The screen has no link to your website, because Apple doesn't let an app send members to pay outside the App Store. On a site that doesn't sell in the app, a blocked member is still refused and signed out as before. Needs the 1.3.25 plugin.
  • Spaces and Courses list in the order your site sets in Fluent Community. Fluent Community 2.12 has a sort for each list in Settings › Customizations: a default order (A–Z, newest, oldest, or Featured, the order you drag the spaces or courses into) and whether members can change it. The Spaces tab and the Courses screen now open in that order and show their Sort by menu only when members can change it. Featured is the last choice in the menu, and appears once you have saved a featured order. Courses had no Sort by menu before and always listed A–Z. The Spaces order no longer comes from Customize › Spaces: if you set one there, set it again in Fluent Community. With a Fluent Community older than 2.12, Spaces list A–Z with a Sort by menu, and Courses list A–Z without one. It comes with the companion plugin's update.
  • Opening a notification email's link in the app marks it read. Fluent Community's notification emails mark a post's notifications read when their link opens your website. When the link opens the app instead, the app now does the same, whether it was closed or already open, and a link to a comment opens on that comment. A link opened while signed out is marked read once the member signs in.

🛠 Fixed

  • A link to your site's My Courses page opens My Courses in the app. Fluent Community's My Courses link is the courses page with ?type=enrolled, and the app opened it on all courses. It now opens on My Courses, as the website does.
  • A space or course bought in the app opens as soon as the purchase goes through. After an in-app purchase from a space's or a course's lock screen, your site let the member in at once, but the screen kept showing the lock until the member left it and opened it again. The screen now reads the space and its posts, or the course and its lessons, again as soon as the purchase is checked.
  • A member who loses a paid space or course sees its lock on the open screen. When you unlinked a purchase in WP Admin › In-App Purchases › Subscribers, or a subscription ran out, your site took the member out at once, but a space or course they had open kept showing its posts or lessons, even after they left the app and came back. Your site sends the phone nothing when that happens; the app finds out the next time it reads its settings from your site, which it does each time the member comes back to the app. When those settings show the member's purchases have changed, in either direction, an open space or course sold in the app reads itself again and shows its lock, or opens, without the member leaving it, and the menus and space lists drop or add it too. This works for each purchase on its own: a member who holds a subscription and a course and loses only the subscription sees its space lock while the course stays open. Access you change some other way, such as removing a member from a space in Fluent Community, still shows the next time they open that space or course. Needs tbc-community-app 1.3.25 on your site.
  • A space bought in the app shows its posts straight away on a site that grants access through FluentCRM tags. The tag the purchase applied added the member to the space, but its feed said "No posts" for up to 15 minutes, or until they signed in again or opened the website, because Fluent Community's list of the spaces they can read was not updated. It comes with the companion plugin's update.
  • A space or course a member wasn't in yet opens as soon as they buy in the app. On a site that grants access through FluentCRM tags or another automation, a space the member wasn't in yet said "Space not found" instead of showing a lock, and a course showed an error. When they subscribed in the app, from Settings or the membership screen, your site let them in, but the screen stayed as it was until they left it and opened it again. It now reads the space and its posts, or the course and its lessons, again as soon as the purchase goes through. Sites that don't sell in the app are not affected.
  • The notification badge corrects itself on every site. Every few minutes the app asks your site how many notifications are unread and sets the bell badge to that number, which fixes a badge thrown off by a notification read on the website, a push for something already read, or a member who turned push off. The app only did this on sites where its own chat was running live, so a site using Better Messages, without Fluent Messaging, or with Messages turned off in Customize kept a wrong badge until the app was restarted. It now does it on every site.
  • Screen readers name the header's buttons and the profile form's fields. TalkBack and VoiceOver read nothing for the logo at the top of every screen, said only "button" for a header's close, share, new-message and chat-members buttons, "switch" for a settings toggle, and read only the value of a profile field such as First Name. Each now says what it is: the logo reads your site's name, every header button and toggle its own name, and each form field its label, in English, Spanish, Greek and Dutch.
  • The iPad's tab bar fits Split View. Putting the app into Split View while it was open left the tab bar laid out for the full screen: the tabs kept their full-width places, the last ones were cut off, and one could sit on top of another. The bar now lays its tabs out again whenever the app's window changes width.
  • The Community Menu's tiles show each space's picture. On its carousel, browse tiles and hero card layouts, the Community Menu drew a space or course with its small sidebar icon, because that is the only picture Fluent Community's sidebar carries. A course with an emoji icon showed the emoji instead of its cover, even with "Show the picture" chosen. Each tile now shows the picture the app's own cards show: a space's thumbnail (Fluent Community's "Thumbnail / Open Graph Image"), else its cover photo, as the Spaces widget does, and a course's cover photo, as the Courses widget does. The icon shows only where there is no picture. The tile's crop, shape and "Fit the whole picture" apply to it, and "Colour tile only" still shows no picture. Menu rows, buttons and chips keep the icon. A Spaces widget showing one space group shows the pictures the same way. The pictures come with the companion plugin's update; with an older plugin the tiles look as before.
  • A space or course picked as a tile shows the picture its space card shows. A Links tile, or a hand-picked Courses or Spaces tile, pointing at a space drew its cover photo, while the app's own space cards draw its thumbnail first. With the companion plugin's update a space tile draws the thumbnail, then the cover photo, then the logo; a course tile still draws its cover photo first, as the course cards do.
  • The end of a page clears the phone's navigation bar. At the end of a post, the reaction and comment buttons sat under Android's three-button navigation bar or the iPhone's home indicator, where they were hard to tap. The same happened at the foot of a blog post, a course page, a space's members, documents, media and pages lists, a space's lock screen, a member's followers list, the blog list opened from the menu, the links editor and a web page opened in the app. Each now ends above the bar.
  • A Call to Action card's button uses your site's button colour. With no colour set on the card, its button was filled with the app's accent colour, which follows your site's link colour unless you set one, and its words were in the text colour your site picks to go on its buttons. A light link colour made those words hard to read. The button now takes your site's button colour, as the app's other buttons do. The card's tinted background still follows the accent, and a colour set on the card still paints both. The Customize › Widget Screens preview draws the button the same way once the companion plugin is updated.
  • A Call to Action card's icon reads on any site colour. On a card with an icon and no picture, the icon sat on a disc of the app's accent colour, which follows your site's link colour unless you set one, and was drawn in the text colour your site picks for its buttons. A light link colour made the icon hard to see. The disc now takes your site's button colour, like the card's button. A colour set on the card still paints both. The Customize › Widget Screens preview draws the disc the same way once the companion plugin is updated.
  • App Lock no longer mentions Face ID on Android. Its line in Settings named Face ID, which only iPhones and iPads have. It now asks for "your face, fingerprint, or device passcode", which fits both. In English, Spanish, Greek and Dutch.
  • Two-factor authentication lines up with Settings. Its cards sat 16 points further in from each side than the cards on the Settings screen you open it from, because the screen added a side margin of its own on top of theirs. They now line up.
  • A member Message Roles stops from messaging is told why, in your words. When the Message Roles add-on refused someone a direct message, a chat opened from a profile told them "This member's profile isn't visible to you", which was the wrong reason, and New Message said member search isn't available. Both now show the Title and Message set on the add-on's Restriction Message card. A field left blank shows the app's own wording in the member's language: "Messaging is limited" and "You don't currently have permission to send messages. If you think this is a mistake, please contact a community admin." It needs Message Roles 1.8.10; with an older Message Roles the screens look as before.
  • A member your Fluent Messaging settings stop from starting a chat is told why. With Fluent Messaging's "who can start a chat" set to moderators, a member it refused saw "This member's profile isn't visible to you" when they opened a chat from a profile, and New Message said member search isn't available. Both now say "Only moderators can start new chats in this community." When the setting also lets members with enough points start one, a member short of them reads "You need 50 points to start new chats in this community." Both are in English, Spanish, Greek and Dutch. When Message Roles is the reason, its own message still shows. It comes with the companion plugin's update; with an older plugin the screens look as before.
  • On a tablet, search fields and filters sit over the list they filter. On Search, Notifications, the member directory, a profile's followers and following, Courses, the blog list, a YouTube playlist, a space's Members screen and the filter chips on a space's Pages screen, the search field, chips and tabs under the title stretched across the whole screen while the list below kept to a column in the middle. They now keep to the list's column. Phones look the same as before.
  • Editing a post keeps its documents and its video. A post with documents lost its files when you edited it and saved. Edit now opens the post the way your site's own editor does, from what your site stores with it: its photos, a video (a link from YouTube, Vimeo or another site, or one uploaded on the website), a poll, a GIF, a voice note, its documents and its topics.
  • Editing a photo post keeps its photos on a site that signs its picture links. Some sites add a signature to every picture link they send out. On those sites, saving an edit removed all of a post's photos, because the app sent the signed links back and Fluent Community did not recognise them. The app now sends each photo's plain link. A site that serves its pictures from another address, such as a CDN, still can't be matched; Fluent Community's own web editor has the same limit.
  • Renaming a group chat saves the new name once. Pressing the keyboard's Done while the new name was still saving started a second rename, so the change was sent to your site twice. It now saves once.
  • The composer keeps to a column on an Android tablet. Android opens the composer full screen, so on a tablet the post's title, the lines you type, the attachments and the toolbars ran the whole width of the display. They now keep to the same centred column as the feed. Phones look the same as before.
  • A purchase you unlink stays unlinked when it renews. After you unlinked a subscription in WP Admin › In-App Purchases › Subscribers, the store handed the next renewal to the old owner's app if it was still open, and the app checked the renewal with your site as that member. Your site then linked the purchase back to them and gave back what it unlocks, a few seconds after each renewal. With the companion plugin's update, the renewal is recorded and the purchase stays unlinked until a member taps Restore purchases or buys it again. The app shows no error for it; if the plans sheet is open, it says to tap Restore purchases. Restore also checks every purchase with your site again rather than answering from what the app checked earlier, so it claims an unlinked purchase even in the same session. In English, Spanish, Greek and Dutch.
  • An iPhone or iPad checks each purchase with your site once. After a purchase, the app checked it with your site a second time when the member closed Apple's "You're all set" message and came back to the app, because each copy Apple hands the app is signed separately and the app told copies apart by that signature. It now tells them apart by the purchase's own number, so the second copy is recognised and costs nothing. Android was not affected.
  • Signing out closes every screen the last member had open. Signing out swapped only the screen on top for the sign-in screen, so the screens under it stayed open, still showing the last member's space, profile or course. When the next member to sign in on that phone followed a link to the same place, the app went back to the old screen instead of loading it for them. Signing out now closes them all, whether the member chose Log out, their session ended on the site, or they deleted or deactivated their account. After a session that ended on its own, signing back in still opens the screen the member was on, loaded fresh.
  • The plans sheet opens full height, with its heading in a title bar. It opened at 60%, so on an iPad, where the sheet is a card in the middle of the screen, the second plan, its button, Restore purchases and the payment terms started below the edge with nothing to show the sheet scrolls. It now opens full height on every device. Its heading sits in the sheet's title bar, so the plans scroll under the bar instead of under the grabber. A space's or course's sheet is headed "Unlock this space" or "Unlock this course", as its lock screen card is, where it said only "Unlock this". In English, Spanish, Greek and Dutch.
  • A member you block while they're signed in is signed out and told why. When a member's Fluent Community profile was set to blocked while they were signed in to the app, by you or by an automation such as a FluentCRM funnel that blocks a member when their paid tag comes off, the app kept them signed in, and every screen showed old or empty content with no message. With the companion plugin's update, the app signs them out the next time they open it and shows your site's own message on the sign-in screen ("Sorry, you cannot access this community."), the words they see if they try to sign in again. That phone also stops getting the community's notifications. Other refusals, such as a private space they aren't in, still sign nobody out, and a pending member still signs in as before.
  • Passkeys are offered only on phones your site vouches for. A phone lets the app use a passkey only when your site's link file for that phone names the app: apple-app-site-association for iPhone and iPad, assetlinks.json for Android. Where the file wasn't filled in, the sign-in screen still offered "Sign in with a passkey" and Settings offered "Add a passkey", and the phone could only refuse. The app now offers passkeys only on the phones your site names. To turn them on, fill in the Deep Linking card on the companion plugin's Config tab. Needs tbc-community-app 1.3.25 on your site.
  • Edit profile no longer saves over changes made on the website. Edit profile filled its form from the copy of the profile the app had kept, so a field changed on the website since then, such as the bio, went back to its old value when the member saved, even if they had changed only their name. The form now waits for your site's current profile before it opens, so its spinner can show a moment longer. If your site can't be reached, Edit profile says so and closes rather than open on the old copy.
  • A member locked out after too many sign-in tries is told to wait when they ask to reset their password. FluentAuth sends no reset email to a network that has hit its sign-in limit, but the app still said reset instructions were on their way, so the member waited for an email that never came. The app now shows FluentAuth's own message, the one your website shows: "Too many attempts from your network. Please wait 30 minutes and try again." An address that isn't registered still gets the usual reply, so the screen never shows whether an account exists. Needs tbc-community-app 1.3.25 on your site.
  • Creating an account no longer closes the app on Android. After Create account, the app could close on an Android phone instead of opening the new member's first screen. The account had been made, so signing in again worked, but the member's first look at the app was a crash. It came from the ticked terms checkbox redrawing as the form finished sending; the checkbox and the sign-up form's choice chips now redraw in place.
  • A space's unread badge clears when the member reads the space in the app. On Fluent Community 2.12, a space's badge counts the posts since the member's last visit to that space. Your website records each visit and the app didn't, so a space opened once on the website kept its badge in the app however often it was read there. The app now records a visit each time a member opens one of their spaces, and the Community Menu's badges update. It comes with the companion plugin's update.
  • A direct message with a community admin or moderator no longer offers Block. Fluent Messaging 2.12 doesn't let anyone block a community admin or moderator, so Block in the chat's menu could only fail. The menu now leaves it out, matching Fluent Messaging, and still offers Unblock for a block made before that rule.
  • An admin reading a space chat they haven't joined no longer sees Leave. Fluent Messaging 2.12 lets community admins and moderators read and post in any space's chat without joining it, so the chat's info sheet offered them a Leave button that could only fail. Leave, and Rejoin, now show only to someone who belongs to the chat.
  • A button that leads to another site hands you to the browser cleanly on iPhone and iPad. A button that points at another site, such as one on a space's lock screen or the welcome banner, opens that site in the phone's browser and closes the page the app had started to open for it. That page could close while it was still sliding in, which can crash the app on iPhone and iPad. It now waits until the page has finished opening, then closes.

🔧 Changed

  • The app checks your site's settings less often. Each return to the app read the app settings from your site again, and Android counts a tapped link or notification, the photo picker, the share sheet and permission prompts as leaving the app, so an active member's phone asked several times a minute. A return now reads them again only when the last read is a minute old or more. A change you make in WP Admin or Fluent Community, such as colours, menus or a feature you switch on or off, still reaches an open app with its next answer from your site: each answer says which version of the settings is current, and the app reads them again when it moves on. If a screen asks for a feature your site has just switched off, the app reads the settings again and the screen says the feature isn't available. Before, that showed WordPress's own "No route was found matching the URL and request method" with a Try Again button. Maintenance mode and a raised minimum app version still reach it at once.
  • Screens re-read your site less often, and a screen you open reads it every time. Every return to the app, every switch back to a tab and every Back from a link re-read everything on the screen, every loaded page of a feed or notifications list included, so opening a link and coming Back loaded the feed under it twice. A tab you switch back to, or a screen you come Back to, now re-reads only what is more than 30 seconds old, and a return to the app re-reads only after a minute or more away. A screen you open still reads your site each time, so a post, a profile, a course or an achievements page shows what your site holds now, including another member's reaction from a few seconds ago. What you change yourself shows at once wherever it appears, on a screen that is open or one you open later: a file you post in a space appears under the space's Documents, a space you join shows on your profile and on Home, and the points or badge a notification tells you about show in your achievements. Pull to refresh still reads at once. The composer's space list, moderation queues, a chat you open and store prices still ask again every time.
  • The same thing is asked of your site once, not once per screen. Several screens asked your site the same question under names of their own, so moving between them asked again. Your space groups (the Spaces tab, the space pickers and the space form) and the full space list (the Spaces tab and the link editor's members-only picker) are now read once and shared, a space's settings screens, its documents and its links manager share the one copy of the space, and a member's profile and Edit profile share one copy of it. A lesson no longer reads its whole course again: it uses the course already loaded and asks only for the lesson, and a lesson you mark complete shows as done on the course page at once.
  • The tablet sign-in panel matches your website's. On tablets and desktops, the panel beside the sign-in and sign-up forms now looks like the banner on your website's login page. Its background colour, picture, logo, text colours and side come from Fluent Community's login customizer, and if the banner is turned off there, a tablet shows the same sign-in screen as a phone. A site that never set up its Fluent Community banner now shows Fluent Community's default light grey panel with the site logo, where it used to show the app's sign-in background under a dark overlay. Phones keep the app's sign-in background, and the panel's words still come from Customize › Sign-in screens.
  • A widget with nothing to show leaves no gap on a tablet. Widget screens lay their widgets out in aligned rows, one column on a phone, two on a tablet and three on a wider screen, each row as tall as its tallest widget. A widget with nothing to show at the moment, such as an events list with no events coming up, still held its column, so the row had a hole the next widget skipped past. Its neighbours now close up, and it takes its place again once it has something to show. Phones look the same as before.
  • More buttons, chips and fields look like the rest of the app. Several screens drew their own, each a little different in shape, size or colour. They now use the app's shared ones, so they look and respond the same everywhere: the option fields and the Cancel, Done and Add More buttons in the poll builder; the Embed and Upload choice and the Embed, Choose Video and Cancel buttons when you attach a video; the Emoji, Shape, SVG and Image choice in a space's icon picker; the topic and visibility chips in the post composer; the topic filter above a space's feed; a sign-up question with one answer to pick; the field and the Cancel and Delete Forever buttons when you delete your account; the details field when you report a post; a quiz's written answer; and New in a space's topic settings. The search screen's "Search for anything" now looks like the app's other empty screens. A one-answer sign-up question can no longer be changed while the form is sending. A member's profile draws its Follow, Following, Notify, Chat and Blocked buttons with the app's shared button too, and a subscription screen with nothing to buy shows the app's usual empty screen.
  • Direct messages open faster. Opening a direct message with someone asks your site for that one conversation, without the four inbox lists the app used to load with it and throw away. Fluent Messaging's own chat opens a conversation the same way. A Fluent Messaging without this option answers in full, as before.
  • The app runs on Expo SDK 58, with React Native 0.88 and React 19.3. It keeps your app on the versions Expo and React Native maintain, so it gets their fixes; screens look and work as before.

🖥 Dashboard & builds — if you build your own app

Ship: Needs a new build — Expo SDK 58 (React Native 0.88), the new expo-blur, and expo-iap 6.0.3 for apps with in-app purchases on; build from the dashboard's Build page, a new Development build included.

  • The setup dashboard is rebuilt around the jobs you do. The row of tabs gives way to a side menu grouped by job: Home, then Make the app (Set up, Build), Ship it (Release to stores, Live updates) and Keep it current (Add-ons & core, Backup & hand-off). Every page, card and field has its own address, so a link from the guide or a check opens the exact place, and Find (Ctrl K, or ⌘ K on a Mac) jumps to a page, card, setting or action by its name, and opens a matching heading in the Setup Guide or the docs in a new tab. The header's readiness pills and version badge are gone: each page in the menu shows how many things on it need you, and each card's header says the same.
  • Home tells you what to do next. "Set up a new app, in order" lists the seven setup steps, each marked done, now, not yet or not needed, with a button that opens it. "Needs you" lists everything that stops a build or a store release, or is worth a look, each with the button that fixes it, and "At a glance" sums up the app. Check everything again reads it all fresh.
  • The steps you take on other websites are on the list too. Enrolling with Apple, creating the app in App Store Connect and Play Console, the Play Console forms, the push key, turning push on in WordPress: 48 steps the dashboard can't take for you, each with a link to the exact page, why it matters, and the guide section that walks it. Many tick themselves once the dashboard can see they're done, from your site's own answers and its deep-link files, or from a store once the app has shipped there. You tick the rest on Home with I've done this.
  • A build, store upload or live update that would fail is stopped first, and you are told why. The dashboard refuses to start a build, upload or send a build to a store, push a listing, its graphics or Data Safety, attach or release a build, submit for review or push a live update while something that stops it is still open, and names each thing. Halting a rollout or pausing a phased release is never refused. The fleet's jobs check every app the same way.
  • Set up Google is one run. From one Google sign-in it creates or picks the Firebase project, registers the app and saves both Firebase files, turns on Cloud Messaging, puts the Android push key on Expo and makes the Play upload robot. It asks before it starts, naming the project it will use (a new one, the one you picked, or the app's own) and whether it will link the billing account you picked. You then invite the robot in Play Console, and the card says exactly what to paste.
  • Release to stores walks a release in five steps. Version and build, What's New, Store page, Marketing (optional), then Send for review and release, each marked done, now or not yet for the store you picked. A greyed button says why it's greyed. After a Google Play listing push it reminds you to press Send for review in Play Console, because Google publishes nothing until then. App Store updates can roll out in phases, and a halted Google Play rollout can be resumed.
  • In-app purchases can run on a test site. The In-App Purchases card has a new setting, Purchases run on: your live site, or a test site you name (a full https address). With a test site set, the card's checks and steps, the store catalog sync and Set up Google's purchase notifications all use the test site, so you can sell and test in the app before your live site sells anything. Everything else, such as the companion plugin's version, deep links and legal pages, still reads your live site.
  • Store marketing from the dashboard. New cards for the App Store's Custom Product Pages, In-App Events, Product Page Tests and App Asset Library: write each one, save a draft, and push it to App Store Connect when you say yes. Google Play gets a walkthrough card linked to Google's own guides.
  • Live updates checks before you push. Beside Push Update, the page lists what is checked (your site's companion plugin, every setting that would stop a live update, and whether native code changed since your last build), and a line says which phones the update reaches. Republish, Roll back and Delete sit in each history row's menu, so a slip of the mouse can't delete an update.
  • All apps shows what each app needs. Each row counts what needs attention and the outside steps left, apart, and a Licence column says whether the app's key is saved.
  • AI agents get a page of their own. Connect an AI agent shows how to connect one, whether one is connected and what it did on each app, and a step that needs your yes is previewed first and asked once.
  • Test credentials tells a waiting Apple agreement from a missing role. When Apple refused the App Store Connect key because an agreement was waiting to be accepted, the test said the key needed the App Manager or Admin role, which sent you to make a new key for nothing. It now names the agreement and where the Account Holder accepts it. Any other refusal says the key needs Admin, because a Team key's access can't be changed after it is made and App Manager isn't enough.
  • The Setup Guide follows the new dashboard. Every page and card name, the Dashboard Tour, and the store walkthroughs match the new pages. It now says Firebase is needed for every build, that the dashboard can make your first Google Play upload, which Play Console forms only you can fill (Advertising ID, foreground service), and what to do when Apple holds a release for you to confirm its countries.
  • Building needs Node.js 22.13 or newer. Expo SDK 58 and React Native 0.88 don't install on Node 20 or an earlier Node 22. Run node -v to see yours; the current LTS from nodejs.org is new enough, and the Setup Guide's Node.js step names the minimum.
  • npm run preflight passes on an app with in-app purchases on. Switching in-app purchases on adds expo-iap to your app, and preflight's lint then failed on core's services/iap/native.ts with "Unused eslint-disable directive", in a file you can't edit. eslint.config.js now tells ESLint that expo-iap always resolves for that file, so preflight passes with in-app purchases on or off.
  • Update Google Cloud CLI works on Windows. The Google Cloud card's update stopped with "Cannot use bundled Python installation to update Google Cloud CLI in non-interactive mode". It now copies gcloud's own Python out first, as gcloud asks, and updates with that.
  • A store or source the dashboard doesn't know is refused by name. Publishing's compare, pull, push, attach, build list and screenshot publish, and the store tools AI agents use, took anything other than play-store as the App Store, so asking for "android" answered "App Store Connect is not configured", which read as a fault. A mistyped source on compare or pull was taken as the live store, so a pull meant for the house defaults copied the live listing over your local text. Every dashboard action that takes a fixed list of values now refuses any other value by name, such as "store must be app-store or play-store, got android", and changes nothing. Leaving the store out still means the App Store.
  • A Play description imported from your site compares as the same after a push. The site saves line breaks as Windows does, Google Play answers with plain ones, and Publishing's compare counted that as a change, so the full description read "differs" forever, however often it was pushed. The listing text now has plain line breaks wherever it is read or saved, a snapshot import included, so a listing imported before this update reads "same" too, is counted against the store's limits with one character per line break, and is saved that way the next time you save it.
  • An App Store submit sends What's New only when it changed. Submitting compared your What's New with the version Apple held character for character, so text Apple kept with Windows line breaks was uploaded again on every submit. It now compares the way the rest of Publishing does, line breaks aside.
  • The fleet reads sites behind Cloudflare's bot protection. Before a build, submit or store release, the fleet reads each app's site to check its companion plugin. Cloudflare's bot challenge turned that read away on a protected site, so the fleet flagged the site to check by hand every time and could never warn that its plugin was out of date. It now reads such sites the way the In-App Purchases readiness check does, and gives up after 10 seconds.
  • In-App Purchases readiness reads a site whose answer starts with a byte-order mark. A PHP file saved with a byte-order mark puts an invisible character in front of the site's app settings, and the readiness check then said the site had not sent them. It now skips that character, as the fleet's site check does.
  • Each app's own pages check its site's companion plugin. The app refuses to run against a site whose TBC Community App plugin is older than the app's core, so every member sees "this site needs updating" until the site updates. The fleet already read each site before shipping, and now one app's own pages do too. While the site is behind, a banner over the Build, Release to stores and Live updates pages says so, and Push Update stays greyed out until the plugin is updated; a site that can't be read gets a banner saying so, to check by hand. The banner's Check again reads the site afresh. Releasing an approved App Store version, releasing a build on Google Play, an Android upload set to go live, changing a Google Play rollout and resuming or completing a phased release wait for the plugin too; halting or pausing never does. A build, an App Store upload, a draft Google Play upload or a submit for review can still go ahead, since review leaves time to update the site. A fleet job now skips a Google Play release or an Android upload that goes live for such an app, as it already skipped an OTA push. AI agents get the same answer from the dashboard's site plugin check.
  • A subscription synced to the App Store is priced in every country. Apple stores a subscription's price one country at a time, and the In-App Purchases sync set it in your base country only. The subscription was on sale everywhere but could be bought only there. The sync now sets the matching price in each of the other 174 countries, as App Store Connect does when you set a price by hand. A price change does the same, and current subscribers keep what they pay. Run the sync again for a subscription made before this update: it adds the missing countries and leaves the rest alone. One-time purchases were never affected.
  • Your store privacy answers match what your app collects. Google Play's Data Safety form, as the dashboard wrote it, said every app collected a postal address, card numbers, purchases and approximate location, and it left out crash reports, members' posts and searches. Apple's App Privacy answers in the setup guide said something else again. Both stores now get the same answers, worked out for your app: an address and purchases only when the app has a shop or in-app purchases, precise location only when you turned the Location permission on, card numbers never, because your payment provider or the store takes them. The App Privacy step on the App Details card lists the answers for Apple. What to do: on Release to stores, Google Play side, press Generate from project on the Data Safety card, check it and push it. Your site must be reachable, since the dashboard reads whether it has direct messages, Fluent Cart and crash reporting on. If you filled in App Privacy in App Store Connect from the old guide, compare it with that step's list and publish the changes. Generate again whenever you switch one of those features, an add-on or a permission on or off.
  • The dashboard loads nothing from public CDNs. Its icons, the install QR code and the changelog's Markdown came from unpkg and jsDelivr, so they went missing without internet and each page load asked those sites for files. They now ship with the dashboard, so it works offline and sends no requests to third parties.

📡 OTA & Internal

  • A picture whose link changes only by its ?ssm= signature or its ?media_key= downloads once. mediaSource() (utils/mediaSource.ts) gives every source a cacheKey from mediaCacheKey(): the link without those two parameters, everything else kept (an avatar's ?v= stays). The picture still loads from its full, signed link. seedUploadedImage() writes under the same key, and prewarmImages() leaves out a link whose key differs, because Image.prefetch can only cache under the link itself.
  • A post edit no longer reads back what its save returned. submitPost() (components/composer/PostComposerContext.tsx) merges Fluent Community's update answer into every cached copy of the post, its own screen and each loaded list page, instead of emitting CACHE_EVENTS.FEEDS; it still emits FEEDS (and BOOKMARKS) when the topics or a document post's files changed, or the answer matched nothing. A new post still emits FEEDS. Module authors: useFeedActions() no longer takes refresh, and its edit no longer passes onPosted.
  • batchRequest() (services/api/batch.ts) no longer warns "partial batch failure" when a path marked optional answers 404 rest_no_route. The startup batch marks the one it picks from the last session's cached flags, chat's unread threads, so a chat the site has switched off since answers that way once and drops out on the next session. Its status stays on the debug line; any other path or failure still warns.
  • When Android destroys and re-creates the activity with the JS still loaded, the app no longer logs it as a provider remount and a failed audio pause. RootLayout restarts the "App tree mounted again" count when it mounts, so the warning counts per root, and logs a re-created root at info; onPlayer() (components/media/audioSession.ts) treats Android's released-object codes (ERR_USING_RELEASED_SHARED_OBJECT, ERR_INVALID_SHARED_OBJECT_ID, ERR_APP_CONTEXT_LOST) as a player already stopped, as it does iOS's ERR_NATIVE_SHARED_OBJECT_NOT_FOUND.
  • The live chat connection starts only on the site's answer from this session. AppConfigContext no longer seeds socketConfig from the cached /app-config copy (it is set ahead of the unchanged-payload bail, like plugin_version) and clears it at sign-out, so a phone whose cached copy predates the site switching its chat off (Fluent Messaging deactivated, or Better Messages taking over) no longer asks /chat/broadcast/auth for a channel and gets a 404 at every cold start and sign-in. Realtime begins once the startup batch lands; the chat polls until then, as it does without a socket. The app follows the site's answer alone: companion 1.3.25 sends socket: null whenever the app's own chat is off.
  • ThreadsResponse (types/message.ts) gains an optional restriction (MessagingRestriction, { title, message }), which Message Roles 1.8.10 sends on GET /chat/threads?user_id= and on a refused GET /chat/users when its role rules are why. useChatMessages returns it beside notPermitted, useMemberSearch beside searchUnavailable (memberSearchRestriction() reads it off the 422), and ComposerNotice takes it as a prop. A blank field falls back to the new keys messages.info.restrictedTitle and messages.info.restrictedBody.
  • MessagingRestriction (types/message.ts) gains optional reason ('moderators_only' | 'points') and points, which companion 1.3.25 sends when Fluent Messaging's own start-a-chat rule refused. When message is blank, restrictionCopy() picks the body from them: new keys messages.info.restrictedModeratorsBody and messages.info.restrictedPointsBody (plural).
  • The cart contract (types/commerce.ts) gains two optional fields: CartData.discounts (CartDiscount[], named rows that are part of totals.discount) and CartItem.total (the line after its discounts). CartSummary lists the named rows first and shows the plain Discount row for totals.discount less them, worked in the currency's smallest unit. CartItemRow, when total is below subtotal, strikes the unit price beside total ÷ quantity rounded to the store's decimals, so every row still reads per unit. A provider that sends neither renders as before. New key cart.originalPriceA11y.
  • spacesApi.createTopic() sends slug: '' as Fluent Community's web form does: FC Pro's saveTopics() slugifies it before falling back to the title, and a missing one logged a PHP 8.1+ preg_replace() deprecation on the site at every topic made inline from the app.
  • pusher-js's own log now goes through createLogger('Pusher') at debug in dev (Pusher.log) in place of Pusher.logToConsole, so a refusal services/pusher.ts already handles, such as a 404 from /chat/broadcast/auth after Fluent Messaging is switched off, no longer prints as a console error. Production logs nothing from pusher-js, as before.
  • The date pickers (a profile or form date field, and Schedule post's date and time) move from DateTimePicker's deprecated onChange to onValueChange + onDismiss, so @react-native-community/datetimepicker 9.1 stops warning in dev. Android's dialog still unmounts on set and on cancel, and iOS's spinner stays open while it turns.
  • A development build's Diagnostics › Tools › Crash reporting has a "Throw a render error" row: a child component throws on its next render, inside the root ErrorBoundary, so the fallback screen members see instead of a crash, and its Try Again, can be checked on purpose. __DEV__ only, so no store or preview build shows it.
  • Module authors: useSurfaceChrome() returns bottomPadding, the scroller's bottom clearance on either surface: the safe-area inset when the screen is pushed, tabBottomPadding when it sits in a tab. Put it on contentContainerStyle.paddingBottom (plus your own gap) in place of hosted ? chrome.tabBottomPadding : insets.bottom, which the core screens and the module-system guide's example now do. <SurfaceScreen> leaves the bottom edge to the content, so a pushed screen that pads nothing ends under Android's navigation bar. A module that reads it needs core 1.3.25.
  • Module authors: HeaderIconButton's accessibilityLabel is now required, so a button with no name fails npm run preflight (tsc) instead of reaching screen readers as a bare "button". Pass the button's name, translated (t('…')), or use HeaderCloseButton (components/navigation/HeaderCloseButton.tsx) for a screen's close X, which names itself. Toggle takes an optional accessibilityLabel too; pass the setting's name.
  • useQueryInvalidation (hooks/useAppQuery.ts) invalidates on a refreshKey change only: it keeps the last key it applied in a ref, which <Activity> keeps. Before, a tab's effects re-ran on every reveal, so one pull-to-refresh on Home or a widget page re-read the welcome banner and every widget (the leaderboard up to 30 times) on each return to that tab, inside their 2-minute staleTime.
  • Module authors: a query's invalidateOn now reaches it while its screen is closed. useAppQuery and useAppInfiniteQuery declare each key's events to utils/cacheEvents.ts, which keeps them for the session, and cacheEvents.emit() invalidates every declared key in one invalidateQueries call: a key on screen refetches, and the next reader of one off screen reads it again. emit() returns a promise that settles once the screens on show hold their fresh copy, and the new CACHE_EVENTS.NOTIFICATIONS fires when a notification arrives (a push in the foreground, or new_count on the ticker poll) or is opened (a bell row or a push). Both hooks re-read when their screen opens or navigation hands it different params, and when the member comes Back to a screen whose data is past its staleTime. The open re-read is one switch, REFETCH_ON_OPEN in hooks/useAppQuery.ts, behind the refetchOnOpen option: on at the screen default staleTime, off for widgets and longer windows. A screen left open under another is not opened again, so a query there still needs its event.
  • Every app response from a site on companion plugin 1.3.25 carries X-TBC-Config-Version, and /app-config carries the same value as config_version (AppConfigResponse.config_version, ResponseHeaderData.configVersion). AppConfigContext reads /app-config again when a response names a version other than the one in the payload in use, once per new value. It skips a header that arrives while a read is running, and shares one read with the route-gone signal below. The once-a-minute read on return and the route-gone read stay. The root layout no longer re-reads the config for X-TBC-Min-App-Version, a setting the config version already covers; X-TBC-Maintenance stays, since a member's bypass role isn't a setting.
  • When a request answers 404 rest_no_route, the API client tells addRouteGoneListener() listeners (services/api/client.ts), at most once a minute per feature (wp-json/<namespace>/<resource>), and AppConfigContext reads /app-config again. Requests that fail together share one read. errorMessage() maps rest_no_route to common.errors.unavailable in the member's language, and the notification settings screen now passes its failures through errorMessage() too.
  • BottomTabs keys its Sortable.Grid on the window width (useResponsive().width) as well as the tab count. A running grid kept its full-screen cells when the bar narrowed into Split View, while one mounted at the new width lays out right, so a width change now remounts it. A grid torn down mid-drag never sends drag-end, so the same change unpins the bar the drag had pinned.
  • auth.login.banner and auth.signup.banner in /app-config carry the banner's look (background_color, background_image, logo, title_color, text_color, hidden, position), typed as AuthBannerLook. AuthLayout draws the picture cover-fit inside a mixBlendMode: 'multiply' view over the colour, as Fluent Community's CSS does, and uses FC_BANNER_DEFAULTS until the config loads. Every colour arrives drawable: the plugin resolves one cleared in Fluent Community's editor to what Fluent Community's page draws, so the app has no fallback of its own. The picture and logo sources are memoized on their URLs, so typing in the form doesn't hand expo-image new sources. useImageAspect also returns the decoded size, which the banner logo uses to draw at its own size up to 200 wide.
  • Module authors: ChoiceChips takes maxChipWidth, which caps each chip and keeps its label to one line, and TextInputField takes containerStyle for the field's outer box, its bottom margin included. makePaint() (components/common/TileGrid.tsx) no longer returns badge: a disc on a tinted panel takes the site's button colour (colors.primaryDark) with textInverse, as the Call to Action card's does.
  • Widget pages (components/widgets/WidgetPageRenderer.tsx) are plain views in aligned rows (gridColumns()), no longer a react-native-sortables surface, so the inView remount after a tab switch goes. A cell that measures 0 tall leaves the flow (absolute, still mounted) until it draws. A long-press (300 ms), or with a screen reader on an Arrange widgets button under the grid where allowHide is on (AccessibilityInfo.isScreenReaderEnabled(); the map's reordering has no screen-reader action), opens sheet/arrange-widgets (ArrangeWidgets.tsx, an OVERLAY screen) over an expo-blur BlurView of the page, with sheet/hidden-widgets (HiddenWidgets.tsx, FIT) for the hidden tray; both share WidgetMapTile.tsx. AppWidgetPage.locked is replaced by allowRearrange and allowHide (both === true in normalizeAppConfig(), so an older companion plugin reads as both off). The member's order stays under tbc_widget_order_<pageId> and the hidden list is tbc_widget_hidden_<pageId> (widgetPrefs.ts, useWidgetArrangement()), both in MMKV. useWobble() takes an angle, swing count and start delay, useCollapsingHeader() returns the header's full height, and useMeasuredGrid() floors a cell to a whole physical pixel. expo-blur (~58.0.4) is a new native package.
  • Module authors: give a chrome: false widget a title in its WidgetRegistration all the same. The page never draws it, but the arrange map names the widget by it, falling back to its id. nameSetting on a WidgetRegistration names a setting the map reads first, HTML stripped (core's content heading, text and image widgets and the Call to Action card use it); a widget without one is named by its title.
  • Core moves to Expo SDK 58: expo 58.0.14 with every expo-* package and @expo/ui at SDK 58's pins, react-native 0.88.0, react and react-dom 19.3.0, and expo-dev-client 58.0.14. The native libraries sit at Expo's pins as well: react-native-gesture-handler 3.3.0 and react-native-webview 14.0.1 (both new majors), react-native-reanimated 4.7.1 with react-native-worklets 0.13.0, react-native-screens 4.30.0, react-native-safe-area-context 5.10.1, react-native-svg 15.16.0, @shopify/react-native-skia 2.14.0, @react-native-community/datetimepicker 9.2.1, @shopify/flash-list 2.3.2 and react-native-keyboard-controller 1.22.7, so expo.install.exclude keeps only Sentry. Outside Expo's list: @sentry/react-native 8.31.0, ahead of Expo's pin for iOS crash fixes the pin lacks (an app started with an invalid DSN, a native log with a callback option set, as beforeBreadcrumb is, and the replay mask's mount), react-native-nitro-modules 0.37.1, react-native-enriched-html 1.1.1, the three @tanstack query packages 5.104.3, @react-native-vector-icons/ionicons 13.1.5, react-native-web 0.21.4 and, for the lint config, globals 17. Core meets three changes SDK 58 brings: InteractionManager is gone, so AppWebView's off-site hand-off waits for its own native-stack transitionEnd (or 1 s) before it leaves; expo-router turns a tab with href: null into a hidden tab that hands focus straight back, so app/(tabs)/_layout.tsx sets it only on the empty and action slots, and a launcher-only Spaces or Activity, a /spaces link and the landing router stay reachable; and expo-file-system's File.write() is now async, so utils/logBuffer.ts writes with File.writeSync() and a log shared from Diagnostics is complete. The native packages need a new build. The expo-iap pin in manifest.json moves from 5.8.2 to 6.0.3, so an app with in-app purchases on gets it at its next core update, in a new build. 6.0 follows OpenIAP Client Protocol 1.0: every store failure arrives as a PurchaseError with a standard ErrorCode, every code services/iap/errorCodes.ts reads keeps its name, and a failed purchase request fires the error listener once, then rejects with the same error. Play Billing stays at 9.1.0, the iOS openiap pod moves from 3.6.1 to 4.0.3, and what the app sends /iap/verify is unchanged. In 6.0, deepLinkToSubscriptions() on iOS presents the same Manage subscriptions sheet Apple refuses to an iPhone app running on a Mac, so the app's fallback there opens the App Store's subscriptions page itself, as it did before; services/iap/ now type-checks against expo-iap's own types as well as the stub. Two config plugins keep SDK 58 builds going: plugins/withIosEnrichedHtmlReactHeader.js gives react-native-enriched-html 1.1.1 (the composer's editor) a forwarding header for its bare UIView+React.h import, which React Native 0.88's prebuilt core no longer resolves, until a stable enriched-html release imports it as <React/UIView+React.h>; and plugins/withAndroidBuildMemory.js sets org.gradle.workers.max=2, gives the Kotlin daemon its own 2 GB heap and limits every module's native (CMake) build to two compile jobs and one link, so an Android build, production included, fits EAS's default 16 GB build machine.
  • Module authors: core builds on React Native 0.88 and Expo SDK 58, so a native package your module adds needs a release that supports them. react-native-gesture-handler 3 and react-native-webview 14 are new majors; under webview 14's types a ref typed bare WebView makes JSX reject every prop, so type it WebView<object> as AppWebView does. React Native 0.88's stricter TypeScript API is held off: tsconfig.json lists react-native-legacy-deep-imports in customConditions, so your module keeps today's types until React Native 0.89 drops the opt-out. Core has left what SDK 58 deprecates: Appearance.setColorScheme('auto') in place of 'unspecified', and ThemeProvider and useRoute from expo-router (ParamListBase and DefaultTheme stay on expo-router/react-navigation). ImageBackground is deprecated in React Native 0.88 and still works.
  • POST /iap/verify carries restore on every call (IapVerifyRequest.restore: boolean; the plugin reads a call without it as a pre-1.3.25 app). It is true from restoreIapPurchases() and for a product beginIapPurchase() is buying (claimingProducts): runSync() and handlePurchase() carry it to verifyAndFinish(), a claim no longer recalls settledThisSession, and a claim that joins a non-claim verify answered iap_transaction_released verifies again as a claim. The new server code iap_transaction_released is non-retryable (the transaction is finished), never settled for the session, logged at info, and shown by SubscribeView as toast.info with the new key iap.errors.transactionReleased; isIapVerifyQuiet() (services/iap/errorCodes.ts) names it.
  • purchaseKey() (services/iap/native.ts) keys an iOS purchase on transactionId (then id) instead of purchaseToken, which on iOS is the per-copy JWS; Android still keys on Play's purchase token.
  • AuthRedirect (app/_layout.tsx) calls router.dismissAll() (when router.canDismiss()) before router.replace('/login') on sign-out. Delete Account (ProfileMenu), Deactivate (useDeactivateAccount) and the renewal gate's Log out (MembershipGate) no longer navigate themselves after logout(); AuthRedirect is the one owner of that move.
  • 'sheet/subscribe' uses the TALL preset (constants/navigation.ts). SubscribeSheet passes its heading to <SheetScreen title> and renders <SubscribeView hideTitle>; the heading and intro moved into the exported useSubscribeHeading(variant, itemKind). requestSubscribeSurface() takes an optional itemKind (IapUnlockCard passes its kind), carried on IapSurfaceRequest and SubscribeSheetInput. The iap.subscribe.titleUnlock key is gone; an unlock uses iap.lock.spaceTitle / iap.lock.courseTitle. <SheetScreen>'s title wraps to two lines, centred, where it was cut at one.
  • IapConfig (services/api/appConfig.ts) gains optional grants, the fingerprint of what the member's purchases open that companion 1.3.25 sends in /app-config's iap block. IapHost holds it beside entitlement in one ref and fires announceIapEntitlementRead() when either changes with both sides known, once per read; a 'config' signal still never re-reads /app-config, and a payload without grants compares the entitlement alone, as before.
  • features.space_mark_seen (companion 1.3.25: true where Fluent Community has Helper::markSpaceSeen()) gates spacesApi.markSpaceSeen() (POST /spaces/{slug}/mark-seen). The space screen calls it on focus for a member whose membership is active and, on success, refetches COMMUNITY_MENU_CACHE_KEY. The flag defaults to off: older Fluent Community has no such route, and a rest_no_route answer makes the app read /app-config again. space_pivot.updated_at leaves the space type; Fluent Community 2.12 hides it, and nothing read it.
  • /app-config's spaces.sort is always Fluent Community's, { default, user_choice, has_custom_order } with no source, and courses.sort relays its Courses sort when Fluent has one. types/listingSort.ts holds ListingSortMode (custom | alphabetical | latest | oldest), normalizeListingSort() and listingSortChoices(); resolveListingSort() (contexts/AppConfigContext.tsx) reads both blocks. CoursesScreen sends sort_by, and the sort is part of its cache key. Module authors: the Sort by control both screens use is SortMenu (components/common/SortMenu.tsx).
  • RootLayout (app/_layout.tsx) reads Fluent Community's notification-email marker (?fcom_en=true&feed_id=<id>) on the launch URL and on each URL from outside the app (readNotificationMarker() and toFeedId() in utils/deepLinkMapper.ts) and calls markFeedRead(), the push tap's mark-read-by-feed call. A marker that arrives signed out waits for sign-in, and the same URL again within two seconds (iOS can hand over a launch link twice) counts once. The mapper keeps a post link's numeric comment_id.
  • getThreadsForUser() (services/api/messages.ts) sends dm_resolve=1. ThreadInfo (types/message.ts) gains optional is_community_admin (DMs) and can_leave_chat (space threads), which Fluent Messaging 2.12 sends; useChatMessages returns canToggleBlock and canLeaveChat from them, and SpaceInfoSheet takes membership ('member' | 'left' | 'none') in place of isLeft. A server that sends neither field reads as before.

1.3.24 — 2026-10-10

📱 What's New — store release notes

  • One installed app switches between your live site and its copies, so site admins can try a change before members see it.
  • Tapping a blog author or commenter opens the right profile. Large text, the hiding header and short Android chat messages look right.
  • On Android, tapping a notification opens the right post, even after the phone closed the app.
  • Voice notes stay on your posts, after an edit too. Adding photos or a video to a post replaces its voice notes, as on the website.

✨ New

  • One installed app switches between your live site and its copies. Try a plugin or a setting on your staging site, or give a client a sandbox of their own to try their setup, from the same app your members use, with no separate build. The copies are set on your WordPress site, in WP Admin › TBC Community App › Config › Staging Site: the staging site's address and who sees it (Admins only, Admins + roles, or Everyone for a demo app), and under Sandbox access, which person gets which sandbox. Admins get the staging site and every sandbox; anyone else gets only what you give them. In the app, Diagnostics › Tools › Site has a Switch to button for each site that person may use, and from one copy you can go straight to another. Diagnostics opens for anyone who has a switch, even when you have Diagnostics turned off for members. Each site keeps its own sign-in and saved data, so coming back to the live site finds you still signed in there. On a copy the header shows a red STAGING badge, and the sign-in screens and every blocking screen (offline, maintenance, outdated site) carry a Back to live site button, so a copy that is down never leaves anyone stuck. Before it switches, the app checks that the copy runs the companion plugin at this version or newer. While the app is on a copy, a link to the live site opens on that copy, whether it comes from an email or a web page, and so does a tapped live-site notification. The phone keeps getting the live site's notifications while on a copy, and a copy it was opened on can notify it too.
  • The Community Menu widget shows the groups you pick, in six layouts. A Community Menu on a widget screen can show every group of your Fluent Community sidebar, only the groups you tick, or every group except those, so a group you add later joins the "all except" ones on its own. It draws them as menu rows (the sidebar look, as before), a carousel, browse tiles, a hero card, buttons or chips. Group headings can be turned off, so a widget showing one group reads as one list under its title. One screen can open with your "Get Started" group as a hero card while another carries "Resources" as rows, all from the one sidebar you arrange in Fluent Community. Set it on the widget in WP Admin › TBC Community App › Customize. A Community Menu saved before this update draws as it did.

🛠 Fixed

  • Pages stay clear of the clock when the header hides. On a screen whose header hides as you scroll, the header took the strip behind the clock and battery with it, so the page scrolled up under them. That strip now stays in the header's colour, and the header slides up beneath it.
  • A post's header stays readable with large text. With a large system font, a post's time and space name could run under the bookmark and menu buttons, or leave a "•" on its own. Both now shorten together, and the dot stays with the space name.
  • Short chat messages keep their last word on Android. A one-line message could lose its last word, or wrap onto a second line it didn't need. It happened in the app's own chat and in Better Messages alike. iOS was not affected.
  • Tapping a notification opens it on Android after the phone closed the app. Android closes an app left in the background for a while but keeps it in the recent apps list. Tapping a notification then started the app on Home instead of the post, chat or screen the notification was about. It now opens the right screen, the same as when the app was running or fully closed. iOS was not affected.
  • Tapping a blog author opens their profile. A blog post's byline, and a comment's author, linked to the member's WordPress name, while the app finds profiles by the community username, which can differ and which members can change. For those members the tap opened "profile not found". With the companion plugin's update, both link to the right profile.
  • Voice notes stay on their post. The composer let a post carry photos, a GIF or a video together with voice notes, but Fluent Community keeps one kind of media per post, so the voice notes were never attached and were deleted two hours later, leaving a player that would not play. As on the website, adding one kind of media now replaces the other, and a post that already has both opens with the one that was saved.
  • A new space role shows in the composer's space list. Made a moderator of a space, or let into one, a member could open the composer's "Post in" list and find "No spaces match" for that space for up to two minutes, closing and reopening the app included, though she could post from the space itself. The list now checks again each time it opens, and shows it is loading until it has.
  • Editing a post keeps its voice notes. Saving an edit to a post with voice notes removed them from the post. They now stay, as they do when you edit on the website.
  • Documents attach every time. Attaching a document to a post sometimes failed with "Sorry, you are not allowed to upload this file type", even for a PDF. The app renames the file before sending it, and sometimes sent it before the renamed copy was written, so the site received an empty file. It now waits for the copy.
  • Crashes at startup on Android reach your Sentry. Crash reporting starts once the app's own code has loaded, so a native crash in the first moment of startup went unreported. On Android 12 and newer the app now reports it from the phone's own crash record the next time it opens.
  • Blocking a member no longer trips an error on Android. After you block a member from their profile, the menu's "Block User" row becomes "Unblock User" and stops being red. Android refused that colour change with an error ("Cannot set prop 'elementColors'"), so the row could keep its old colour. The row now takes its new colour.
  • Changing the member directory's sort shows the top of the list. Switching between Joining Date, Last Activity and Display Name kept the member who was first on screen in place, so the new order opened part-way down the list. It now starts from the top.
  • Screen readers can close a banner and play its video. A welcome or course banner's close button, and the play button on any video before it starts, were read out as an unnamed button. They are now "Close" and "Play video".
  • "Spaces in a group" shows only spaces. The Spaces widget's "Spaces in a group" also drew the courses and custom links that group holds in your Fluent Community sidebar, as tiles that looked like spaces. It now shows the group's spaces only. For the whole mix, use the Community Menu widget with that group ticked.

🖥 Dashboard & builds — if you build your own app

Ship: Needs a new build — the Android notification tap fix is native; build from the dashboard's Build tab.

  • The staging site comes from your WordPress site, not from the build. You can now change, re-point or remove the staging site without building again. That makes the Config tab's Staging URL field, the Dev Server (Staging) button and npm run dev:staging unnecessary, so they are gone. The Site URL field now sets every build profile, development included, to your live site's address, and Diagnostics › Tools › Site switches a Dev build to staging. To aim Metro at some other site while testing, start it with EXPO_PUBLIC_SITE_URL set. If you had a Staging URL set before this update, your Dev builds still start on the staging site. To move them to the live site, open eas.json and set EXPO_PUBLIC_SITE_URL under build › development › env to your live site's address.
  • Release notes cover every version a store update carries. With "Use each app's own changelog notes" ticked, a Fleet View release now fills each app's notes from the What's New of every core version since that app's last store build, because members get all of them in one update. Each version's first line comes first, newest first, and whole lines are left off the end to stay under 500 characters. Untick "Include notes from every version since each app's last build" to use the app's own version only. A version you pick from an app's dropdown stays picked.
  • Checks that pass say so. A passing check on the Config tab used to read as if something were missing ("App Store Connect API Key ID not set…" when it was set). Each now says what is set or uploaded.
  • An AI agent connected to the dashboard no longer gets your secrets back. The Sentry auth token in config_state and the licence keys and token in prefs_get / prefs_set come back masked, with the last four characters showing. The Config tab still shows them in full. A masked value an agent sends back keeps the one you saved, and a file the agent downloads, such as a service-account key, is saved to disk instead of landing in its chat.

📡 OTA & Internal

  • plugins/withAndroidNotificationTapBeforeLoad.js (a new config plugin in app.json) gives MainActivity an onNewIntent that hands a Firebase tap's extras to expo-notifications' NotificationManager.onNotificationResponseFromExtras() before Expo's ReactActivityDelegateWrapper drops intents that arrive before the app has loaded (expo/expo#49254). React gets a copy without google.message_id, so a loaded app still gets each tap once. The plugin can go once Expo ships expo/expo#51192.
  • The sites a viewer may switch to arrive in /app-config as features.staging_sites (a list, staging first). On the live site AppConfigContext remembers that list, so on a copy the Tools tab still offers the others. services/siteChoice.ts keeps the chosen site in its own MMKV store and constants/config-core.ts reads it before any URL resolves, so SITE_URL stays one site for the whole session. IS_STAGING is that runtime state; EXPO_PUBLIC_USE_STAGING is gone. The main MMKV store and the three SecureStore sign-in keys carry SITE_STORAGE_SUFFIX, which is empty on the live site, so nothing moves on members' phones. setup/docs/logging.html#staging-switch describes it.
  • TopHeader draws a fixed status-bar strip in the header's colour above the header, and AppWebView's top padding stops at the status bar, so a web tab's hide decision counts only the space it really gains.
  • On Android the chat HTML engine ends each hard line with a hair space, which the line breaker lets hang, so a bubble sized to its text keeps every word.
  • cross-env is no longer in package.json; only dev:staging used it.
  • The composer's media rule is one helper, keepOnlyMedia(): photos, a GIF, a video and voice notes each clear the other three once something of their own has landed (pickImages() now resolves to how many images landed, so a cancelled pick clears nothing). oneMediaKind() in components/composer/feedPayload.ts applies the same rule to an edited post's starting state, a kept draft and every body buildCreateFeedData builds.
  • useSpacePickerOptions (the composer's space picker and search's scope sheet) no longer sets WIDGET_STALE_TIME: each open shows the kept list and refetches it, since can_create_post changes with a role or a join and the persisted copy outlived both.
  • services/api/documents.ts namedCopy() awaits File.copy(), which is asynchronous since expo-file-system 56 (copySync() is the synchronous one); unawaited, uploadDocument sometimes sent a copy that was still empty (WordPress: application/x-empty, 422).
  • feedsApi.updateFeed() sends the post's id in the body as well as the URL, as Fluent Community's web editor does: FluentPlayer keeps a voice note already on the post only when the body names the post being edited.
  • Sentry's enableTombstone is on (services/sentry.ts): on Android 12+ a native crash is reported on the next launch from the OS's own crash record, so one in the first moment of startup (before Sentry starts from JS) is no longer missed.
  • The blog comment sheet links an author through fcom_author_slug alone. Its fallback to /wp/v2/users/<id> (the route FluentAuth's "Disable users REST" refuses members) and getWpUserSlug() are gone; setup/docs/blog-system.html says what the slug is.
  • CommunityMenuWidget filters the sidebar tree by group, then draws its five tile layouts through TileGrid; menuRowTile() and tilePresetOptions() in components/home/widgetLink.ts are shared with the Spaces and Links widgets, and widgetSettings.ts gains getIdList().

1.3.23 — 2026-10-08

📱 What's New — store release notes

  • Attach a document to a post and it goes up with the post, under its own file name.
  • Tap a link to a post and the app opens that post.
  • Blog posts show who wrote them, with a tap through to the author's profile.
  • On Android, you can scroll to the last choice in every sheet.

🛠 Fixed

  • A post's web address opens the post. Tapping a link to a post, in a message, an email or on the website, opened the post's space instead. Fluent Community writes a post's address with its name, and the app only recognised the address that ends in the post's number. Both open the post now.
  • Documents attached in the app are posted. Fluent Community Pro turned down every post that had a document attached in the app, with "No documents found to save", so the post never went up. It goes up now, and the document keeps the name it had on the phone. On Android it used to arrive under a random name such as "a3d59f1a-….pdf".
  • Members see who wrote a blog post. On a site that hides its member list from WordPress's REST API (FluentAuth's "Disable users REST" setting does this), members saw "Unknown" as every blog post's author, with no photo and no link to the profile. Admins saw it correctly. The companion plugin now sends the author along with the post, so everyone sees the name, photo, verified tick and profile link.
  • The course-complete sheet closes only itself. It closes on its own after 4 seconds. If something opened over it in that time (a tapped notification, a link), that screen closed instead. The sheet now closes itself only while it is in front, and otherwise waits for Continue.
  • The end of a half-height sheet is reachable on Android. A sheet that opens at 60% laid out its content for full height, so the last options of a long list, such as the Pull-to-Refresh Style picker in Settings, sat below the screen and couldn't be scrolled to. The content now ends at the bottom of the screen.

🖥 Dashboard & builds — if you build your own app

Ship: OTA-safe — JS-only, push from the dashboard's OTA tab.

📡 OTA & Internal

  • A post link without ~id goes to /feed/[id] with the slug and by_slug=1, from deep links and notification routes alike. The post screen loads by slug (getFeedBySlug) when the flag is set or the id isn't a number, under its own cache key, so a post whose slug is all digits opens the right post. setup/docs/deep-linking.html describes it.
  • Blog reads no longer ask WordPress to embed the author (_embed=wp:featuredmedia,wp:term,replies). The byline is post.fcom_author, added by the companion plugin; types/blog.ts and setup/docs/blog-system.html describe it.
  • A new document post sends document_ids (each upload's media_key), the shape Fluent Community Pro validates. uploadDocument sends a copy under the file's own name from a cache folder of its own, one per upload, removed afterwards.
  • On a 60% sheet, SheetScreen follows the sheetDetentChange event and lifts its bottom edge by the part that is off screen. partialSheetDetents() in constants/navigation.ts replaces opensPartly().

1.3.22 — 2026-10-07

📱 What's New — store release notes

  • Menus, sheets and pickers are now your phone's own, so they look and work like the rest of your iPhone, iPad or Android phone.
  • Hold a chat message to react, reply, copy or delete from one pop-up.
  • Choose how you start a comment, if your community allows it: a floating bar, a round button or a box above the comments.
  • Screen readers name every button in the post composer.

✨ New

  • Menus, sheets and pickers are the phone's own. iOS and Android now draw them, where the app used to draw look-alikes of its own. A post's "…" opens the system menu (a menu on iPhone and iPad, a Material dropdown on Android). Sheets (who reacted, the cart, a space's or group's info, the space picker, GIFs, polls, the schedule, the launcher and the rest) open as a bottom sheet on a phone and a centred card on an iPad, where the side panel used to be. They take the system's grabber, swipe, dimming and keyboard handling, and VoiceOver and TalkBack read them like any other system screen. The post composer is Apple's page sheet on iPhone, a centred card on iPad and full screen on Android, and its pickers open as sheets on top of it. The screen behind a sheet stays as it was instead of reloading, and the launcher is sized to its tiles.
  • One pop-up for reactions and message actions. Holding a chat message opens a single card: the reactions above the message and Reply, Copy and Delete under it. Holding the react button on a post (with Multi Reactions) opens the same pop-up. It replaces the chat's bottom sheet and the old reaction strip, and every reaction and action in it has a name for screen readers.
  • Choose how comments start. In Customize › App Screens › Feed, a new Comment box setting picks how members start a comment: the floating "Write a comment…" bar (as before), a round floating button, or a box at the top of the comments. It is set apart from the post box, so the two can differ. With "Members can change this in the app" on, each member can pick their own under Settings › Comment Box.

🛠 Fixed

  • Screen readers name the composer's buttons. The photo, GIF, poll, video, audio and file buttons, and the bullet list, quote, code and link buttons, were unlabelled icons that VoiceOver and TalkBack skipped, and bold, italic, strikethrough and the heading buttons were read as single letters. Each now says what it does and whether it is on, in all four languages.
  • A member's profile opened offline no longer crashes. Opening a profile the app had never shown before, with no connection, closed the screen with an error. It now shows the profile's loading placeholder and fills in when the connection returns. Other screens that load the same way do the same.
  • Turning an Android phone sideways while typing keeps the keyboard up. Rotating the phone with the keyboard open in Edit Profile, the space form or the composer closed the keyboard, and the member had to tap the field again.

🔧 Changed

  • Who reacted reads more clearly. Each row in the list of who liked or reacted shows the member's avatar, their name with the verified tick, their @username and, with Multi Reactions, the reaction they picked at the end. Profile badges are left off the list, and the reaction tabs are pills.
  • The Messages inbox shows the verified tick without profile badges. Badges stay where the member is the point: their profile, member cards, the leaderboard and author lines.

🖥 Dashboard & builds — if you build your own app

Ship: Needs a new build — the system menus come from @expo/ui; build from the dashboard's Build tab.

  • If you wrote your own module, check it before taking this update. The app's sheets and menus are now the system's, and the JavaScript sheet library behind the old ones is gone, along with the components built on it: BottomSheet, Sheet, SidePanel, SheetMenu, AnchoredEmojiPopover and ChatReactionStrip (ComposerSheet is now Composer, useMessageMenu is now useMessageTaps). A module that imports any of them stops bundling on 1.3.22. The replacements are smaller: declare sheets in the module's manifest and open them with openModuleSheet() / openModuleSheetFor(), put a menu on a button with NativeMenu, open a choice from code with showActionSheet() and a pick from a list with selectFrom(). A module that used @gorhom/bottom-sheet directly declares it in its own manifest dependencies. The Module System and Responsive System docs walk through each one, and every add-on we sell already uses them.

📡 OTA & Internal

  • Sheets are routes the system presents: screens under app/sheet/, each registered once in NATIVE_MODAL_SCREENS (constants/navigation.ts) with a preset (SHEET: 60% then full; TALL: full only, for a form that must be in view; FIT: sized to its content; OVERLAY: see-through). utils/sheetResult.ts opens one (openSheetFor() awaits its answer, openSheet() answers nothing) and delivers the answer once the sheet has gone: a request answers once, a save started in a sheet that is swiped away still answers (track()), and a request whose screen never mounts ends after 5 seconds. SheetScreen is every sheet's frame.
  • Android lays a sheet out at its tallest detent, so a sheet with buttons at its foot opens full (TALL) or fits its content (FIT), never at 60%; SheetScreen warns in development when a footer lands on a 60% sheet. Every list in a sheet is a FlashList with nestedScrollEnabled, on screen from the first frame (loading, error and empty states inside it), because Android's sheet finds its scrolling list once, as it lays out.
  • hooks/useInView.ts says whether a screen is still in view under a sheet or the pop-up, so tab screens, the widget page and chat polling no longer treat a sheet as leaving. useScreenBeneath() reads the screen under any see-through presentations: the sign-in guard and the profile gate judge a sheet by that screen and close only the sheets on top before they move the member.
  • One KeyboardAvoidingView (components/common) holds the iOS modal offset five screens copied, and a scroll area holding an input takes avoidingScroll (flex 1, minimum height 1), so Android never sees it at zero height mid-rotation.
  • @gorhom/bottom-sheet is removed from package.json and @expo/ui is declared (it was already installed through expo-router). UserListRow takes a subtitle line instead of badges.

1.3.21 — 2026-10-02

📱 What's New — store release notes

  • Notifications for comments, replies, mentions and messages arrive within seconds.
  • New chat messages show up while the chat is open, space group chats included.
  • Verification codes fill six boxes as you type, and a code you paste fills all six.
  • Buttons match your community's colours, and YouTube videos play again.

✨ New

  • Bigger or smaller post buttons. In Customize › App Screens › Feed, a new Post actions group sizes the react button, the comment button and the reaction summary on every post (the react button and summary on comments too): Small, Medium or Large. Medium is the size they've always been, so nothing changes until you pick another.

🛠 Fixed

  • New chat messages arrive while a chat is open, even when the site's live connection is refused. When a site's Pusher keys don't match, Pusher refuses every chat channel but leaves the connection open, and the app took the open connection to mean live, so it never switched to checking for new messages. A member saw nothing until they left the chat and came back. The app now counts the connection as live only once the member's channel is accepted, and checks every 8 seconds until then (only while a chat is on screen).
  • Space chats are live however you open them. A space's group chat opened from a notification or a link, before the Messages inbox had been visited, joined no live connection for that space: new messages showed only after leaving and coming back through the inbox. An open space chat now keeps its own live connection.
  • Logging out no longer leaves errors in your site's log. Every screen still open behind Settings (the notifications, the feed, a space) asked the site for its data once more after the session had ended. Fluent Community answers that with a server error and a "You need to be logged in" line in the site's PHP log. The app now asks only while signed in.
  • Signing back in opens Home. After logging out from Settings (or any screen) and signing in again, the app opened that same screen. It now opens Home. A link someone tapped while signed out, or a notification tapped when their session had expired, still opens where it points once they sign in.
  • After you post, the feed stays at the top. The page used to slip down by one post's height as your new post arrived, so your next pull-to-refresh only scrolled it back instead of refreshing. Now it scrolls to your new post and the first pull refreshes. A new comment and a chat opened at a message scroll the same careful way.
  • Modern chat bubbles keep their text on the left. A one-line message whose time didn't fit beside it was pushed against the bubble's right edge, with a gap on its left. The text now starts at the left and the time sits bottom-right, in core chat and Better Messages alike.
  • The Members widget's tiles line up. A member with a short name kept their badge on the name's line while a longer name pushed its badges below, so one tile's time sat a line higher than the rest. Badges now always sit on their own line under the name, and the time sits at the bottom of every tile.
  • The app no longer asks the site twice when the site has said no. A request the site refuses (a page that doesn't exist, no permission, a rejected value) answers the same the second time, so the app now retries only when the site couldn't be reached, timed out or failed on its side.
  • A space's Documents no longer says "Oops! Couldn't load documents" to a member the space keeps out of them. It shows an empty library, the way it already did for a site without Fluent Community Pro, and it stops asking the site again for an answer that can't change.
  • Turning live chat off on your site takes effect in apps that are already open. When a site switched realtime chat off, or handed chat to another system, apps still running kept the old connection settings and asked your site to authorise chat channels on every sign-in and reconnect, which the site answered with an error each time. They now drop the live connection as soon as they hear the setting changed.
  • Blog posts and pages with their own styling read cleanly. A post that carries a stylesheet or a script showed its code to members as screens of text before the article. The app now leaves those out, and a post's picture lightboxes stay closed until opened, instead of every full-size picture and its Close button appearing inline.
  • Screen readers name the back button. The back arrow at the top of every screen, and the in-app browser's back and close buttons, were read out only as "button". They now say "Back" and "Close", in the member's language. The cart's buttons say what they do too: "Increase quantity of …", "Decrease quantity of …", "Remove …" and "Remove coupon …".
  • Screen readers reach everything inside a pop-up sheet on iPhone and iPad. VoiceOver read the post composer, the space picker and the GIF, poll and video pickers as one blank "Bottom Sheet" and could reach nothing inside them, so a member using it could not write a post. Every field and button in a sheet is now read out and can be used on its own.
  • Screen readers name the chat's info button. The info button at the top of a Better Messages chat was read out only as "button". It now says "Details", in the member's language.
  • A sheet the keyboard pushes up stays below the status bar. On an iPad in landscape, the post composer lifted by the keyboard slid under the status bar, and dragging its handle down opened the iPad's app switcher instead of closing it. Sheets now stop under the status bar, where their handle closes them.
  • The keyboard matches the app's light or dark mode on iPhone and iPad. A light app on a device set to dark mode brought up a dark keyboard when writing a post. The keyboard, and Apple's own pickers and alerts, now follow the mode the app is in.
  • The Messages screen says when your community has messaging off. On a site without Fluent Messaging (switched off, or chat handed to another system), opening Messages from an old notification or a link showed "Oops! No route was found matching the URL and request method." It now says messages aren't available on this community, and doesn't ask the site for conversations it doesn't have.
  • Members are no longer stuck on "Under maintenance" after it ends. With maintenance mode on, the app asked your site for its settings again on every answer it got (dozens of times in a few seconds) until its own flood guard stopped it, and the guard then also stopped the maintenance screen's 30-second check, so members stayed on it until they closed the app. The app now asks once when maintenance starts and finds its end with that 30-second check.
  • Switching the companion plugin off for a moment no longer signs members out. While TBC Community App was deactivated (a plugin updated by hand, say), WordPress answered the app's session refresh with "no such route", and the app took that as the session being refused and signed out every member who opened it. It now keeps the session until the plugin answers again.
  • Coming back online fixes the screen you were on. A screen that showed "No internet connection" stayed on it once the connection returned, until the member tapped Try Again. It now tries again by itself.
  • A space you've opened before still opens offline. It showed "Oops!" under the banner promising saved content; it now opens on the copy saved the last time, and refreshes once it can.
  • A member removed from a space stops seeing it. The space and its posts stayed on screen, and in the app's saved copy, after the site stopped showing it to them. A refresh now says the space isn't available and drops the saved copy.
  • A deleted post says so plainly. Opening one (from a notification that arrived before it was deleted, say) showed "Oops! Post not found" with a Try Again that couldn't bring it back. It now has its own screen and a Back button; a lost connection still offers Try Again.
  • "Change Avatar" reads clearly before a photo is added. On the profile-completion screen and Edit Profile, a member with no photo saw their big initials showing through the "Change Avatar" label. The circle is now a solid colour until there is a photo.
  • Verification codes go into six boxes. The sign-up and email codes were typed into one plain field that didn't show how many digits were left. They now fill six boxes as you type, and a code pasted or filled in from the message lands in all six.
  • Edit Profile shows a choice field's options on the form. A custom profile field set up as radio buttons, like a Yes/No texting opt-in, opened as a dropdown that hid its two answers behind a tap. Its options now sit on the form with the field's instructions above them.
  • Push notifications arrive in seconds. A comment, reply, mention or message used to wait in your site's job queue until its scheduled tasks next ran: about two minutes on average on a server that runs them every five, and on a site that relies on visitors to run them, until someone visited. Your site now sends each notification right after the action that caused it, without making the member wait. A send to more than 100 people still goes through the queue.
  • Turning messaging on reaches apps that are already open. A member with the app open kept seeing "Messages aren't available" until they left the app and came back. Messages now asks your site again when it opens.
  • Asking to join a private space says so. A member who tapped "Request Access" got no "You're all set!" and saw the space open as if they had been let in (an empty feed, the same after a restart) while their request still waited. The app now shows that the request was sent and keeps showing it until a team member lets them in.
  • The Push Log shows failed pushes. On every site that installed the companion plugin before its log learned to record errors, a push that failed was missing from the log and wrote a database error to the site's PHP log instead. The update adds the missing columns.
  • Buttons use your site's button colour. Fluent Community gives a site a link colour, a button colour and a button-text colour chosen to read on the button. The app filled its buttons with the link colour and wrote the button-text colour on it, so on a site whose two colours differ the app's buttons didn't match the website's, and a light link colour could make the label hard to read. Buttons, the new-post button, send buttons, filled badges, checked boxes, quiz choices and calendar days now use the button colour with its own text colour.
  • A quiz comes before "Mark as Complete". On a quiz lesson the button showed before the member had taken the quiz, over the quiz's own Next, inviting them to skip it. It now appears once the quiz has a result. When the lesson requires a pass and the attempt failed, the bar says what score completes the lesson instead.
  • Titles on pictures are readable. On space and course cards, and the picture tiles in Home widgets, the dark fade covered only the text itself, so a title on a pale thumbnail (a screenshot, say) faded into it. The fade now covers the lower half of the picture and is darkest behind the title.
  • A course's description keeps its paragraphs and lists. "About This Course" ran everything together into one block of text. A welcome banner switched on with nothing in it no longer leaves an empty white bar at the top of the course page or the feed.
  • Like, Comment and Reply are easier to tap. Each has a full-size touch area now, without looking any different.
  • Settings cards and empty profile covers are calmer. Cards on thread details, Edit Profile and the space settings were drawn in the "selected" tint, which made them look selected and turned olive in dark mode; they now use the normal card colour. A profile with no cover photo showed a solid block of the brand colour; it now shows a soft tint of it.
  • Asking into a private space from the Spaces list says so. "Request" on a private space's card sent the request but showed nothing, so it looked lost. It now says "A team member will review and let you in soon.", as the space's own page does.
  • A sheet closes when another screen opens over it. With a sheet open (a badge's card, a book's schedule, the cart, who reacted), following a link or tapping a notification opened the new screen with the sheet still floating on top of it. The sheet now closes as the new screen opens, the way Back closes it. The post composer stays open, so a post or an edit you were writing is still there when you come back.
  • Searching Messages shows every match. Collapsing a section of the inbox also hid that section's search results, so a search could look empty when it had found conversations. While you search, every section now shows its matches; collapsed sections close again when the search is cleared. The space chats section is now called "Spaces", as on the website.
  • Long posts stop on a whole line. A collapsed post was cut at a fixed height that sliced its last visible line in half above "Show more". It now stops just under a full line and fades into the card.
  • Switches on Android read clearly as off. The off track took the site's pale border colour, often tinted with the brand colour, so an off switch looked half-on. It is a plain grey now. iPhone switches are unchanged.
  • Explanations are easier to read. Helper sentences on settings screens (two-factor, notification settings, form hints) used the faintest grey, hard to read in dark mode. They now use the secondary text colour; timestamps and other small details stay faint.
  • The welcome banner fits on a tablet. A tall banner picture filled most of the first screen. On tablets and wider screens it is now never taller than a 16:9 picture. Phones are unchanged.
  • The react button matches the buttons beside it. With Multi Reactions on, it was drawn far larger than the comment button and faded until you reacted, so it looked switched off. It is now the same size as its row (or the size you pick in Customize), a clear grey before you react and full colour after.
  • A space's name shows once. It was in the top bar and again on the cover. The cover carries it now; the top bar picks it up once you scroll past the cover.
  • A course's and a member's name show once too. A course page had its title in the top bar and on the cover, and a profile had the member's name in the top bar and under the picture. The top bar now stays empty until you scroll past the cover or the profile block, and stays empty while the page loads.
  • Notification settings looks like Settings. The big bell and repeated title at the top are gone; one short line explains the screen, and every switch is where it was.
  • Moving from a post's title to its text keeps the keyboard up. On Android, tapping the text box while typing a title put the keyboard away and dropped the composer, so a second tap was needed to type. The keyboard now stays and the cursor lands where you tapped.
  • The keyboard goes when you put the composer away. Dragging the post composer down while typing in the post's text left the keyboard up and hid the text behind it, and swiping the composer closed left the keyboard over the feed. The keyboard now goes with it, in the reply composer too.
  • The post box shows only where you can post. In a space where only admins (or only verified members) can post, every member still saw the post box and the new-post button, and a post written there failed with "An internal error occurred". The box now follows the space's own rules, and tapping Join in an open space brings it up at once. Choosing a space for a new post lists only spaces you can post in.
  • Sheets on a tab keep working after you leave the tab. On tabs such as Activity and Spaces, once you had switched away and come back, the tab's sheets stopped passing on what you chose: Sort by › Oldest closed without sorting until the app was restarted, and a sheet closed by tapping outside it wouldn't open again.
  • iPhones no longer crash when a sheet moves while its list scrolls. Dragging a sheet that holds a list while the list was still scrolling could close the app on iOS.
  • YouTube videos play again. Tapping play left the player on its cover picture, because the player's page and the library driving it no longer understood each other. Videos now play. On some phones the first tap loads the player and a second tap, on YouTube's own play button, starts the video.
  • Notifications can be switched on again without restarting. If a phone failed once to register for push notifications (on a weak connection, say), turning notifications on again did nothing until the app was restarted. It now tries again.

🖥 Dashboard & builds — if you build your own app

Ship: Needs a new build — native updates (Expo SDK 57 patches, Sentry 8.29); build from the dashboard's Build tab.

✨ New
  • Control the dashboard from an AI agent. The dashboard ships an MCP server (setup/mcp/server.js): Claude Code, Claude Desktop, Cursor or any MCP client gets the dashboard's controls as tools (check an app, change its settings, build, submit, release, push an update, run a fleet job) and waits on long work with wait_until. Anything that reaches a store, Expo, Google Cloud or members' phones does nothing until you say yes. Setup in the docs hub: "Control the Dashboard from an AI Agent".
  • The fleet checks each app's site before shipping. Building, submitting or releasing an app whose site still runs an older companion plugin than the app's core is flagged "site plugin old" (members would see "this site needs updating" until the site updates); an over-the-air push to such an app is skipped, since it reaches members within minutes. A site the dashboard can't reach is flagged to check by hand.
  • Saving just an app name fills in what follows it. The fleet and agents can now rename an app the way the Config tab does: the slug, scheme, package name and store IDs follow unless they've frozen, and a version can be stepped with bumpVersion. The Config tab works exactly as before.
🛠 Fixed
  • A Google Play production release reads "released", not "live". Google still reviews an update after it's released, and its API can't show that; Play Console's Publishing overview does.
  • A new Site URL saved from anywhere keeps app.config.ts in step, so a fleet job or an agent can't leave "Site URL out of sync".
  • A fleet development or preview build is no longer warned "already built — stores refuse duplicate versions". Only a production build can clash with a version a store has seen.
🔧 Changed
  • In-app purchases build with expo-iap 5.8.2. An app with in-app purchases switched on moves to it with this core update. The purchase library is native, so it arrives with the new build.

📡 OTA & Internal

  • The app's log says more for whoever reads it: a failed request names its method and endpoint (not only the status), the auth guard logs where each sign-in and sign-out sends the member and why, and a warning fires if the whole app tree ever mounts a second time (a provider above it changing shape, which restarts every screen).
  • Every dashboard route an agent can drive carries an action block (what it does, its inputs, what it changes, whether it needs a confirm), listed at GET /api/actions; the route table is pinned by a test.
  • The shared emoji popover (AnchoredEmojiPopover) takes a labelFor so each of its buttons has a name for screen readers; the Multi Reactions picker passes the reaction's name ("Love", "Like", …). Its icon buttons said nothing before.
  • A chat message's photos are their own component, components/message/ChatPhotos.tsx (one at its own shape, several as a square grid, across the bubble's media width), so a messaging module draws photos the same way core chat does; the Better Messages module uses it. Core chat looks the same. The reply quote both chat bubbles drew by hand is one component too, components/message/ChatReplyQuote.tsx.
  • The Moon and Circuit pull-to-refresh scenes draw with Skia's path builders. They redrew their paths every frame with calls Skia has deprecated; the scenes look the same and the log is quiet.
  • Dependency pass within Expo SDK 57: expo 57.0.27 with its matching expo-asset, expo-constants, expo-linking, expo-notifications, expo-router and expo-updates patches; @sentry/react-native 8.29.0, @tanstack/react-query (and its persister) 5.104.1, @shopify/flash-list 2.3.3, react-native-keyboard-controller 1.22.6, react-native-sortables 1.10.1, i18n-js 4.5.4 and react-native-web 0.21.3. Major versions past what SDK 57 pins wait for the next SDK.
  • The YouTube player logs when it is ready, each state change and any error it reports. Before, a video YouTube refused to play failed without a word in the log.
  • tokens.touchSlop(box) holds the 44dp hit-area rule once, for the react, comment, reply and header buttons. A page whose title appears after its cover tracks the scroll without crossing to JavaScript on every frame, and a sheet listens for screen changes only while it is open.
  • Closing the composer while its draft was being saved no longer leaves an unhandled error behind.

1.3.20 — 2026-09-29

📱 What's New — store release notes

  • Pick a chat look, Classic or Modern. Swipe a message to reply, or press and hold it for reactions, Reply, Copy and Delete.
  • Signing in is steadier: one tap sends one code, and if you were signed out the app tells you why.
  • Your cart shows the same notes as the website, like an item that sold out or a coupon that no longer applies.

✨ New

  • Two chat looks, Classic and Modern. Choose one in Customize › App Screens › Chat, and members can pick their own in Settings. Modern looks like the messaging apps members already use: your own messages tinted in your app's main colour with a small tail, the time tucked onto the last line, a run of messages from one person joined up, and reactions as chips on the bubble. Classic keeps the website's look. Both pack message text more tightly than before, and the reply, react and ⋯ buttons that stood beside every message are gone, so the bubbles get that width back. In both, swipe a message to reply, or press and hold it for reactions, Reply, Copy and Delete. Better Messages chats follow the same look, and photos show at their own shape instead of a cropped square.

🛠 Fixed

  • Security: a link from outside the app can't open a page inside the app's signed-in browser. The check that should have stopped it never matched.
  • Signing in is steadier. Sign In locks while it works, so a double tap sends one code, and Resend waits 60 seconds. A member who was signed out is told why on the sign-in screen. A failed passkey no longer reads "code incorrect", and a refusal from FluentAuth or Fluent Community shows their own words.
  • Two-step sign-in settings are safer. Removing your last sign-in method on a community that requires one warns first, and new recovery codes stay on screen until you tap "I've saved these".
  • Log Out ends the session on the site too, and Sign up opens your site's own custom sign-up page when Fluent Community has one.
  • The cart shows what your store's cart page shows: an item it dropped, not enough stock, a coupon that no longer applies. Quantities follow your store's limits, a subscription shows what renews, and prices use your store's number format.
  • A post with only a photo, GIF, video, poll or document says "Write something before posting" in the member's language, instead of an error code.
  • A list that shrinks while you are on another tab no longer crashes the app.
  • The same message twice in a row shows one toast, not two stacked copies.
  • Tapping a search result in Messages opens it the first time, instead of only closing the keyboard.

🖥 Dashboard & builds — if you build your own app

Ship: Needs a new build — Expo SDK 57 patch updates and expo-iap 5.8.1; build from the dashboard's Build tab.

✨ New
  • Crash reports point at your app's own code. Every build and every OTA push uploads its source maps (the OTA banner says whether they arrived), each app's releases are named after its bundle ID so two apps on the same version stay apart, and each server error status is its own issue.
  • The store cards show build signing, read live from Expo. The Google Play card says whether your upload keystore is on Expo. The first Android build makes it, so the terminal command for it is gone. The Apple App Store card says whether your distribution certificate is on Expo and until when, turns amber 30 days before it expires, and shows the one-time command only while Expo has no certificate it can use.
  • A missing, expired or expiring iOS certificate is a build check. It warns in the BUILD badge, on the Builds tab and in the fleet grid, and it is re-read on every page load and every Check again. It is a warning, never a failure: a Dev build signs with its own certificate.
  • A client snapshot imported onto a fresh core needs no fixes by hand. On an app still fresh from core, the import list ticks every row, so core's shipped colours no longer quietly keep the client's out. An app counts as fresh only while its name is still core's placeholder, so an app you have started branding never gets rows ticked for you. The app switcher and the fleet grid show each app's current name, not the one saved when it was added.
🛠 Fixed
  • The Google Cloud card tells you to sign in, not to sign in again, on an app that was never signed in to Google. On an app with no Firebase project yet, it says to connect Firebase first instead of pointing at a Set up robots button that isn't there.
  • A new submit key's test shows its result. The result is kept on the dashboard against that key, so Use the new key is never offered beside a test that shows nothing.
  • The purchase-check key's test quotes Google Play's refusal and names the permissions to check, instead of telling you to invite a robot that is already invited.
  • Purchase checks stays a warning until the new purchase-check key is uploaded to your site. The card used to call the switch done while your site and Play Console still used the old robot.
  • The Play Console invite advice matches what works. The card's Your turn steps and the setup guide say to set the robot's permissions in the invite itself, at app level, and to invite a robot again if it was invited without them. Permissions added to an existing robot took more than a day to reach Google; a fresh invite works in minutes.
  • Signing is read again after every Expo sign-in, sign-out or project change, and when a store is switched on, so the store cards never show the last app's answer. Where one bundle ID has certificates under two Apple teams, the card reads the one for this app's team.
  • Saving a store ID works on an app with no ios or android block yet in app.json, and the suggested Android package puts "app" in front of a part Android would refuse (one starting with a digit, or a Java keyword).
  • An app whose name is still blank is titled with its name from the app list, and the App Identity placeholders are the names a white-label app ships with: My Community App, MyCommunityApp, mycommunityapp, com.mycommunityapp.community.
  • Core's placeholder Expo project ID no longer makes the slug row warn that changing the slug would break an EAS link.
  • Checking your licence from the dashboard no longer sets the version your store account lists for this site to 0.0.0.
🔧 Changed
  • The Google Cloud card is built for setting up one app. It reads only that app's own Google project, so it no longer looks through your other projects or lists which apps share a robot. One refresh icon checks the sign-in and reads the robots again; the separate Refresh robots button is gone. The purchase-check steps drop the lines a first setup never needs and finish by sending Google Play's test notification, then checking that your site shows it as Signed by Google.
  • The dashboard keeps your tab when you switch apps or refresh the page. A new browser tab still opens on Config.
  • The Config tab's OTA Updates card is gone. Its locked "Branch: production" only repeated the channel you pick on the OTA tab. The OTA tab's Push Update now says which builds a push reaches, and the updates-URL check points at Build Service, where that URL lives.
  • The setup guide drops the Android signing command, says when the iOS one is needed (including after a certificate expires), and no longer tells you to wait out a Google Play 403.
  • In-app purchases build with expo-iap 5.8.1. Nothing to set: an Android build still uses Google Play, and a store refusal keeps Apple's own wording.

📡 OTA & Internal

  • For modules: the in-app browser takes a pageScript (run once after the first same-site page loads, never from a link) and plays media inline, setRecordingMode() in components/media/audioSession owns the audio setup for recording, and ChatInput takes hideAttach. Better Messages' calls and voice messages use them.
  • For modules: the chatReactionPicker slot is gone. A module gives chat its reaction set through ChatReactionOverrides (contexts/ChatReactionOverridesContext.tsx), and core and module chats draw their bubbles with ChatBubbleFrame. A module that filled the old slot needs updating.
  • For modules: useSafeListCallbacks (hooks/useSafeListCallbacks.ts) keeps a list in a tab slot from crashing after it shrinks while hidden, wallClockDate() in utils/formatDate.ts reads a date plus HH:mm, useLiveMenuTarget keeps a message's sheet on the live message, and NowPlaying's load() takes resumeAt, a position saved elsewhere (startAt is gone). Better Messages, Book Club and Calendar need core 1.3.20 for these.
  • Expo SDK 57 patch updates: expo 57.0.26 and eleven expo packages.
  • The two store cards share one Expo read per page, and the push and signing checks read Expo through one query.

1.3.19 — 2026-09-19

📱 What's New — store release notes

  • See who is around: a green dot on a member's picture means they were active in the last few minutes.
  • Levels now show on members' pictures across the app, the way they do on the website.
  • Coming back to the app keeps your place in the feed, and times like "5 minutes ago" read in your own language.
  • Photos and videos attach without asking for access to your whole library.

✨ New

  • A member's level rides on their avatar. Member cards — the directory, a space's members, followers and following — and the profile header draw the level number on the picture, where the web portal shows it. It follows the community's own leaderboard: with the leaderboard switched off, no level is drawn.
  • Member cards show who is online. The avatar carries a green dot for anyone active in the last five minutes, as the website's directory does. A course page's instructor shows their badges beside their name.
  • Six pull-to-refresh scenes are rebuilt. Kaleidoscope, Warp, Clockwork and Circuit follow your pull with weight and a flick, and light themselves from the app's theme (daylight in light mode, lamp-lit in dark), the way Ocean, Aurora and Moon already did; Spinner and Minimal are redrawn crisp.
  • Android shows Google Play's own payment-issue message. When a subscription's renewal payment fails, or a price rise needs the member's agreement, Play's message appears in the app once per launch so it can be fixed without leaving.

🛠 Fixed

  • Signing up finishes with the second step your community requires. Where a community asks every account for two-step sign-in, a new member now sets it up as the last step of signing up — an authenticator app or a passkey, the same screen signing in uses — and lands in the app when it is done, exactly as on the website. Before, they were let in without it. Communities that do not require it see no change. Once the account exists, sign-up has one way out: if the app cannot finish it (the step has to be done on the website, or its email could not be sent), it says the account is ready and opens sign-in, instead of leaving the member on a form that could only answer "username taken".
  • A member below the first level shows the first level. A points total under the lowest level's floor — a negative balance, say — was resolved to the highest level instead, on the profile's About tab.
  • Lists open on the shape of what is coming. Followers and following, a space's members and scheduled posts had placeholder rows wired in that a spinner above them always beat; a space page and a lesson opened on a spinner; the leaderboard was empty until the board arrived; and the group and space info sheets listed their members differently from the likes sheet. Each now draws placeholders of its own content while it loads. Retrying a space's member list after it failed shows those placeholders instead of a blank screen.
  • Coming back to the app keeps every page of the feed you had scrolled. Returning from the background refetched only the first page and dropped the rest, so a member far down the feed landed back near the top. A pinned post also stays where it is after a reaction or an edit, and loading more never cancels a refresh.
  • Relative times are in the member's language. "5 minute ago" was always English, and never plural, on every device.
  • Text shows as written. A blog excerpt no longer ends in a literal "[&hellip;]", and inbox previews, reply quotes and copied messages show "&" and "'" instead of "&amp;" and "&#039;". Copied messages keep one line break per paragraph.
  • Flag, family and skin-tone emoji stay whole when picked as a space or link icon, or when one leads a tile's label.
  • Audio attached to a new post is sent. It was dropped on create (an edit kept it), so the post went out without it. Editing a post with one image shows that image as its attachment.
  • Photos and videos attach without a library permission prompt. The system picker needs none, so a member who once tapped Don't Allow is no longer locked out of attaching.
  • Rate app opens the review page, the app picks the first of the device's languages it actually ships rather than only the first, and on iOS the launch screen fades out.
  • Rotating a tablet keeps your place in the list instead of scrolling it back to the top.
  • The cart matches the store's own. A store that shows prices including tax gets those prices in the app, and quantity limits, add-on details and unit prices follow the store's rules.
  • Subscriptions behave the way Apple and Google expect. Manage subscription on iOS opens Apple's own sheet in the app, and the App Store page where that sheet cannot show (an iPhone app on a Mac); Restore on iOS checks with the App Store first, so a purchase made on another device comes back; a purchase whose check failed is retried when the app returns to the foreground; and a plan's "/ 3 months" is the period the store bills.
  • The update, maintenance and lock screens always draw their icon, including on a device that cached the app's settings before gate wording existed.
  • A course instructor with no photo shows two initials, like every other avatar.
  • Photos posted from the app are scaled to 1400 pixels wide, as the website's own composer asks for. A phone photo was stored — and downloaded by every member scrolling past it — at full size. Uploads also say whether they are for a post or a comment, as the website does, so a site that sets its own width for either gets it.
  • Deactivating your account is offered only when it can work. On a profile that is not active, the request came back as a plain success and signed the member out with nothing deactivated; the option no longer shows there, and a session that has expired says so.

🔧 Changed

  • Android haptics follow the phone's touch-feedback setting. Android 11 and newer play the system's own haptic of the same weight; older Android plays none, instead of a hand-tuned vibration. iOS is unchanged.
  • Deactivation goes through Fluent Community's own profile endpoint, the same one the website uses — its privacy check, its record of when, and its hooks.

🖥 Dashboard & builds — if you build your own app

Ship: OTA-safe — JS-only, push from the dashboard's OTA tab.

✨ New
  • Readable Crash Reports on the Config tab. The Sentry organisation, project and auth token save with the save bar and go to the app's Expo project as build variables, so crash reports arrive with readable stack traces.
  • Each store has its own on/off switch in the header of its card — Apple App Store, Google Play, Readable Crash Reports and In-App Purchases. The last store on stays on and says why.
  • Each build on the Publishing tab says when it was built and which core it came from, so the build you attach to a release is the one you meant.
  • Quick Commands lead with the dashboard and gain Check Code, the lint-and-typecheck pass to run after changing a module. Check Code now refuses a module that imports from another module, which breaks the day that one is deactivated.
  • .easignore has a YOUR ENTRIES block that core updates carry over, so a line you add for a credential file survives the next update.
  • An OTA push checks it can reach its phones. The push dialog asks EAS for the finished builds the update would reach and compares their native fingerprint with the project's, so a change that needs a new build is caught before it is pushed. It replaces the hand-kept "needs a build" flag — the header pill and the fleet chip — which missed anything it was not told about.
  • A Google Cloud card signs each app in to Google and answers three things in plain words: what the app uploads, pushes and checks purchases with, and whose robot (Google service account) each one is — with the evidence behind Show details. It installs and updates Google's gcloud tool itself: on Windows and Linux from Google's own archive, with no installer and no admin prompt; on a Mac through Homebrew, which also adds the Python gcloud needs. An app that does not ship to Google Play is told there is nothing to set up.
  • Set up robots builds an app's own Google Play robots in its own Firebase project — the robot that submits builds and, for an app selling in-app on Google Play, the one that checks purchases and the notification topic Play posts to. It reuses what already exists, checks billing before switching anything on, and a new key replaces the old one only after it passes its test. Before you press it, the card names anything that would stop it: access you are missing, or no billing on an app that sells on Android. Once everything is built, the card says Robots are set up and the button becomes Check again. The purchase-notification subscription it makes never expires — left to Google's default, it is deleted after 31 days with nothing to deliver, which a small site can go without a renewal.
  • The Push Notifications card connects a new app to its Firebase project. Pick the project from those your Google sign-in can reach, and the app is registered there and both config files are downloaded. Under each file the card shows whether Firebase and Expo hold the same one, with one button to fix each; Firebase keys nothing uses are listed for deletion, asked first, and a key another app on the same project uses is never offered.
  • Handoff Export gives an app's owner its snapshot without your Google key, your crash-report settings and your notes.
  • The iOS bundle ID fills itself from the app name (com.<appname>.community) and locks, like the slug, with the Android package following it. Renaming an app changes everything derived from it until a change would break something: the slug and scheme stop following once the app is linked to Expo, the store IDs once it is linked to Expo or has Firebase config.
🛠 Fixed
  • A Google Play action can no longer break an upload. Google allows one open edit per account and a new one silently invalidates the last, so Review & publish, a pull, a release, a rollout change, a screenshot publish or Test connection — pressed while a build was uploading — killed it with "This Edit has been deleted". Every action that opens an edit now refuses while an Android build is uploading, and says to wait instead; one that is only waiting for its build to finish blocks nothing, so a rollout can still be halted.
  • The Publishing tab stops reporting changes that are not there. Opening it asked Play for the status, the builds and the comparison at once on one account; the comparison lost that race, and a screenshot count it could not read was reported as a store with none — which the step offered to fix by re-uploading. A count that cannot be read now says so, and is never counted or swept up by Select all. A listing the store could not be read for never shows as matching either, and a confirmation says the number it would remove is unknown rather than 0 — on Apple's side too, where a build list that failed to load reaches the release planner as a failed read instead of as "upload first".
  • The Builds list no longer stalls behind the command queue. It read its builds through eas-cli, which queues behind every build, submit and OTA push — exactly when an upload is being watched. It reads over HTTPS now, through the one function the Publishing tab and the fleet both call.
  • Google Play's What's New can no longer ship twice, or not at all. Apple's box already emptied itself each version and refused an empty submit; Play's kept last release's text forever and only warned when it was blank, so the next Android release went out carrying the previous one's notes unless somebody remembered to retype them. Release now refuses an empty box on an app that has shipped — a first release has nothing to be new since, the same exemption Apple gets — and the box empties once its text is on a release: by Release, by the fleet release, or by Update What's New on live release. Only text that actually went out is cleared, a save that fails stops the release instead of shipping the older text, and coming back from a fleet release re-reads the listing so the tab cannot write spent notes back. Between releases the comparison shows Play's What's New as store-only; it is no longer counted as a change to publish, because a listing push never carried it, and Select all leaves it unticked so a bulk pull cannot put last release's notes back in the box.
  • Linking an existing Expo project takes its slug too. A project whose slug differed from the app's left every EAS command failing afterwards.
  • A core update keeps what is yours. It never overwrites an existing NOTES.md, finds your module and language blocks by their marker line rather than the first mention of them, and core's version wins for every package core ships, so an SDK upgrade cannot land with old pins.
  • The Install Dependencies banner appears whenever a declared package is missing, not only when nothing is installed, and waits for a background install already running.
  • The save bar says Saving… and locks while a save runs, and the header switches no longer throw away edits you had staged.
  • Switching apps no longer turns Hide sensitive back on for an app where you had turned it off.
  • The Branding Assets link says so, and the snapshot tab counts Dutch among your languages.
  • An upload is capped by the bytes that arrive, not only by the size it claims, and one refused part-way gets a clear "too large" answer instead of a dropped connection.
  • A store field the dashboard no longer uses stops showing as a difference Compare could never clear.
  • A snapshot restore keeps your in-app selling confirmations. The Sell-in-your-app and Paid Applications ticks travel both ways between sites and the managed wizard, and a step unticked in the snapshot no longer unticks one the destination already confirmed. Extra packages are compared one by one, so a fresh restore pre-ticks them, and an app with no modules shows no empty Modules row.
  • A core update from the Updates tab no longer runs a full install when no dependency moved. The server already knew to skip it; the page asked for one anyway.
  • A module update that drops a screen removes that screen's route file. It stayed behind pointing at a screen that no longer exists, which breaks the next build.
🔧 Changed
  • The store is asked once per tab open, instead of three times for the same answer.
  • Updates are supported from the previous two releases. Bridges that only served 1.3.16 and older are gone; a project on one of those should update to 1.3.18 first.
  • Each app signs in to Expo with its own access token. Log In to EAS still opens Expo's sign-in in the browser; the dashboard then keeps a personal access token for that app, named after it in the account's token list on expo.dev. With no shared login to take turns over, different apps' commands run side by side, and only commands that change an app wait for that app's line. Status reads the account from Expo and shows the plain CLI version. A sign-in interrupted by a restart finishes on the next status check, and signing in again retires the app's previous token. The sign-in an app had before does not carry over: after updating, press Log In to EAS once on each app.
  • Builds need eas-cli 21.3.0 or newer, the first that hands the project's owner and id back from eas init directly.

📡 OTA & Internal

  • The docs carry the 1.3.18 work: a new Two-Factor & Passkeys page, the quiz question types and settings in the courses guide, the space rows and the admin philosophy in the feature list, and the FluentAuth 3.0.3 floor stated. The registration, two-factor, state, architecture, components and forms pages describe sign-up as it now ends, and stop naming files that are not there.
  • The project's package.json no longer lists a courses script. It pointed at a seller-side folder that was never in your download, so running it could only fail.
  • The docs, the setup guide and CLAUDE.md match the dashboard and plugin as they ship: modules are switched on and off with Activate and Deactivate in the Modules card, never by editing the registry; push, Sentry and the custom scheme point at the Config cards that set them; the Google Cloud card, the Push card's live checks and Handoff Export are documented, with a note in every walkthrough they replace; and every system page and the setup guide were checked line by line against the code, with every dashboard screenshot retaken.
  • A cleanup across the app: API services have one way in, components export by name, 23 file-private helpers stop being exported, 236 unused import React lines go, and comments describe the code as it is. Nothing a member sees changes.
  • At launch and on return to the foreground the app posts only the purchases the site is still owed, instead of every purchase; Restore still re-checks them all.
  • HTML entities are decoded by the entities package (pure JavaScript, nothing native).
  • Performance tracing is actually off: any sample rate, 0 included, switched it on, so every screen recorded traces and threw them away. Crash reports are unaffected.
  • The space media grid runs on the same list engine as the feed, and the activity ticker's poll is TanStack Query's own interval — a re-render no longer restarts its timer.
  • manifest.json no longer carries lastNativeChangeVersion; the fingerprint check above answers the question it stood in for.

1.3.18 — 2026-09-17

📱 What's New — store release notes

  • Sign in with a passkey — your face or fingerprint, no password to type.
  • Set up an authenticator app, and keep recovery codes for the day your phone is not with you.
  • Quizzes now ask every kind of question your course sets, and show the answer key when you are done.
  • A new pull-to-refresh scene draws tonight's real Moon and the sign it is passing through.

✨ New

  • Moderators review flagged content where it sits. A reported post or comment carries a banner that opens its reports, with Dismiss, Unpublish, and Approve for something Fluent Community has held. A verdict updates the card in place, the way the web does — the banner appears without a reload, an unpublished post leaves the list, and a card that has already been decided cannot be reviewed again. Reports are never kept on the device, so a decision is always made against a fresh one.
  • Quizzes ask every kind of question your course does. True/false, fill in the blank, put these in order, match the pairs and match the pictures all render in the app, alongside the choice questions it already handled. After an attempt the answer key and the explanation are there to read, and the quiz follows the lesson's own settings for one question at a time and for shuffling.
  • The space menu is the one your community set up. A space's menu now follows Fluent Community's own admin-managed menu — its order, its labels, its icons, and what each member is allowed to see. A row switched off on the website is not in the app either. Space admins edit that menu from the app.
  • Space admins manage members from the app. Add people, change a role, remove someone, and approve or decline join requests from a Requests tab that carries the number waiting.
  • Space Settings is one screen with tabs. It opens on General, with Menu and Links across the top, plus Fluent Community's own editors for pages and the lock screen. A tab switch replaces the screen without a slide, and one back leaves settings.
  • Pages are managed in the app. List them, filter, drag to reorder, publish, show or hide one in the menu, duplicate, copy a link, delete, add. Only a page's content opens Fluent Community's block editor, and it opens straight onto that page.
  • Sign in with a passkey. Where the community allows it, the sign-in screen offers a passkey instead of a password: no username, no password, the credential the phone answers with is what names the account. Where an account owes a passkey after its password, the phone's own sheet comes up as the second step, with a recovery code as the way through if the sheet is dismissed. A build without the native half simply doesn't offer it.
  • Add, name and remove passkeys from Settings. A Passkeys section lists what the account holds, when each was added and when it was last used. Renaming and removing are there, and removing asks first. The list shows even on a phone that cannot create another, because a passkey registered on the website still belongs to the account. A passkey added with nothing else on the account arrives with its recovery codes, shown once — without them it would never be asked for at sign-in, because losing the phone would leave nothing to type. Where a passkey is still waiting on a second way in, the list says so plainly and offers the codes that change it.
  • Set up an authenticator app, in the app. A Two-factor screen under Settings says whether an app is paired and when, sets one up three ways — open the app on this phone, scan the QR from another device, or copy the key — turns it off, and shows how many recovery codes are left with a way to get a fresh set. The QR is drawn by the site, because the link it encodes carries the shared secret. Recovery codes are shown once, before anything moves on, and are never written to the device — and an account that already has a set keeps it, because pairing an app must never quietly retire codes somebody has printed and filed.
  • A community that requires a second step no longer locks members out. Where the community requires one and the account has none, the pairing now happens on the sign-in screen itself and finishes the sign-in — instead of sending the member to the website, where they could not get back.
  • Answer an authenticator or recovery code at sign-in. One field takes either shape, the way Fluent's own form does, and the screen says which one it is asking for. The emailed code keeps its own wording and its Resend link; the other two hide Resend, because there is nothing of ours to send.
  • A new pull-to-refresh scene: the Moon. It draws tonight's real Moon — the true phase, the twelve signs on a gently tilted orbit ring, the current sign glowing at its real progress, and a countdown to the next one. The reading is in all four languages and everything is computed from the device's date. Pick it in Settings, alongside the Ocean scene (which is the scene formerly called Liquid).

🛠 Fixed

  • Signing in works again on a community using Fluent's own two-step sign-in. Its version 3 refuses any login it cannot show its own form for, which is every login the app makes: a member with email codes switched on could not get in at all, and was told their password was wrong. The app now asks what the account still owes and answers it. A factor the app cannot answer yet says so and points at the website rather than leaving the member guessing.
  • The paywall no longer says "nothing for sale" after a blip. Store products were held for five minutes, so a momentary outage at Apple or Google was remembered for the rest of that window. They are asked for on each mount; both stores answer from their own cache anyway.
  • Badges and the level ladder stop re-asking a struggling site. They are buyer-authored reference data that changes about yearly, but they were being refetched every two minutes of scrolling — and while the endpoint was failing, every newly drawn name re-asked, twice, for as long as the member scrolled. Both now hold for an hour and don't retry on mount. The profile's About tab also stopped asking for leaderboard settings on every open when the leaderboard is switched off.
  • A wrong sign-in code counts as a failed login, so the site's rate limiter and security log see it.
  • Text on a red button reads in dark mode. The red never changes with the theme, but its label borrowed the colour used on the app's own buttons, which turns near-black in the dark — so every filled red control (a destructive button, the Blocked pill, the delete-account confirmation, the swipe-to-delete panel, the unread badge) was hard to read, and unreadable when disabled.
  • A space's moderators get their moderator rows on its posts. The space id arrives from Fluent Community as text and was compared against a list of numbers, so somebody who moderates a space was never recognised on its own posts.
  • Reporting content the community declines to accept a report on says so quietly. Reporting a moderator's post raised the developer error screen over the toast that had already explained it.
  • Fluent Community's page editor opens in the app. It draws its canvas in a frame the in-app browser was blocking, so the editor waited for something that never came and gave up. Those frames load, and the editor's own "Back to Pages" arrow is hidden — it led to the website's list, which the app now has its own screen for.

🔧 Changed

  • The app cannot flood your site. Identical requests are counted, and the 51st inside a minute is refused for the rest of the session and reported once. It trips on repetition rather than failure, because the loop that prompted this ran against a site answering normally — one phone repeating a registration call took a community's site down. The busiest legitimate repeater we measured runs at twelve a minute.
  • Three painted pull-to-refresh scenes leave core. Aquarium, Jungle and Cherry Blossom are gone, with their artwork; the ten drawn-in-code scenes remain. A member who had one of those chosen sees the plain spinner until they pick another. Painted art belongs to module packs now.
  • Widget pages show one empty frame before tile grids, galleries and text widgets paint. That is the cost of dropping a width hint the page passed down, which needed every widget to agree about which box it measured.

🖥 Dashboard & builds — if you build your own app

Ship: Needs a new build — react-native-passkey 3.6.2 is native, and app.config.ts gains webcredentials: for iOS. Passkeys do not exist for anyone until a store build ships; the app hides every passkey surface where the native half is absent.

  • Before you build with passkeys: the companion plugin must be live on the site first. Apple fetches the site-association file through its own CDN and refreshes it within 24 hours, and an Android build must be signed with the certificate whose fingerprint the plugin publishes.
  • Downloading a build works properly. Download follows Expo's CDN redirect and keeps the artifact's real extension, opens a Save As dialog with a tidy name, and streams the file into the folder you pick. A failed download now removes the empty file it created — two of those were mistaken for real builds in one week — and says "Download failed" once rather than twice.
  • Review notes ask whether the site is limited by country. A country block sits above WordPress, so nothing in the project knew about it, and the generated notes told Apple there was no geo-gating — while a reviewer outside the allowed countries met a sign-in that refused them. The notes now state the limit and ask the reviewer to use a network inside it, quoting the error the app really shows.
  • Review notes handle nonprofits and donations. A new question records registered status and ID, printed as one line a reviewer can verify. Donations cite their own Apple guideline rather than the one for goods and services, and the wording no longer claims the app sells nothing while describing a donation checkout.
  • Submit for review takes your What's New with it. Typing the notes, publishing them to the version and then submitting was three steps with nothing to decide in the middle one, so the Publishing tab's Submit now puts What's New on the version as it submits. The box still starts empty each version, and an update with an empty one is refused before anything is sent — Apple will not review it. Notes drafted for a language the version does not have are left alone rather than creating it half-filled.
  • A refused App Store submission says why. Apple answers the same status for "this version is already on the submission" and for a version it will not accept, and the dashboard read both as the first — so it submitted an empty submission and showed "does not have any items" instead of the field Apple was actually refusing. It now asks what the submission holds, and every App Store error carries Apple's per-field reasons rather than its generic summary.
  • The pre-1.3.10 app-list converter is gone, except the one step that gives an id to a saved app that has none — removing that bricked the dashboard for an older app list, which is now covered by a test.

📡 OTA & Internal

  • Two-factor and passkey work spans the plugin and the app: five self-service endpoints for the authenticator app, four for passkeys over Fluent's own store, two public passkey routes for signing in, and the site-association files that let both phones offer the app's passkeys. Fluent decides every question — who may hold a passkey, whether a code matches — so the app and the website can never disagree about an account.
  • The cleanup pass that added private caches and memoised state is undone where it cost more than it saved: badge definitions, the level ladder and a startup flag read through the standard path; getAuthToken is a plain async read; tile grids measure themselves. Each was a saving nobody had measured, and three had already been patched for a wrong-state bug.
  • /app-config sends one colour wire. The plugin used to resolve the app palette and then rebuild Fluent's section bags from it, shipping the same theme twice on its hottest payload. Who notices: a phone still on a store build from before core 1.3.4 (late July), on a site using app-only colours, shows the app's default colours until it takes a store update. It cannot crash.
  • The manual push composer takes id lists only. A stale Tools tab left open across a plugin update now says "No users found for the selected audience" and works after a reload; a narrowed audience can never turn into a broadcast.
  • Five add-ons and member-perks drop the shared-script self-heal guard. A site running an old sibling plugin zip beside a new one loses admin tab behaviour until it updates. tbc-profile-completion keeps its guard on purpose: its screen shows Twilio credentials behind a toggle that script injects.
  • The leaderboard reads a row's value rather than the duplicate points field an older tbc-gamipress sent.

Dev Bundle · full source · lifetime licence

The complete source to a native community app for Fluent Community.

Real React Native (Expo), not a webview wrapper — white-labeled to your brand and shipped to both stores under your own name and accounts. Your code, your app.

100%of the React Native codebase — yours to read, modify and extend
2stores from one codebase, on your own developer accounts
1×buy once. No per-member fees, nobody able to switch your app off
0terminal. Builds, submissions and OTA updates run from a browser

Three parts · one bundle

The app, the WordPress side, the dashboard.

Click a tab. Every capture opens full size. Deeper pages: The App, The Plugin, The Dashboard.

Dev Bundle › What’s inside
Choose a view

What members install

Native screens, not a website in a costume

Feeds, spaces, courses, messaging, profiles and push, in light and dark. Every screen below is a real capture from the demo app.

The app home screen, built from widgetsThe app home screen, built from widgets
Home, built from widgets you arrange.
The activity feed on a phoneThe activity feed on a phone
Feeds, posts and comments.
The course list on a phoneThe course list on a phone
Courses and progress.
The launcher menu on a phoneThe launcher menu on a phone
Everything that is not a tab.

Where you shape it

Change the app while watching it change

Menus, widgets, whole screens and the theme are arranged in WordPress and drawn on a live phone as you edit. What you save reaches members the next time they open the app — no build, no store review.

  • Twenty widgets to compose screens from
  • Screens locked in your order, or opened up for members to rearrange and hide widgets
  • Nine app screens, each with a page of settings
  • Subscriptions sold in-app, verified by your site
The Customizer editing a launcher item beside a live phone preview
Editing a launcher item, previewed on a real phone frame.

How it ships

The parts that usually need a developer, from a browser

Cloud builds on EAS, store submissions, over-the-air updates and version bumps — for one app or a whole fleet of them.

  • Store access by API key — no Apple password, ever
  • Store listings written once, sent to both
  • Every client app on one screen, each with its own build login
The dashboard's fleet view, every managed app and its stateThe dashboard's fleet view, every managed app and its state
Every app you manage, with its build and release state.

What you get

  • Full source code — the entire React Native codebase, yours to read, modify and extend
  • Lifetime license — buy once, own it forever. No per-member fees, no SaaS lock-in, nobody able to switch your app off
  • iOS + Android from one codebase — build and push to your own stores with Expo EAS
  • White-label setup — site URL, logo, colours, assets, ship. The browser dashboard handles builds, submits, OTA updates and version bumps
  • Run it for clients — agencies build and manage apps for their own customers

Built in · out of the box

  • Activity feeds, spaces and my-spaces, member directory, profiles
  • Real-time messaging
  • Courses and quizzes (Fluent Community) — every question type, from choice and true/false to ordering, matching and fill in the blank
  • Passkey sign-in, authenticator apps and recovery codes (FluentAuth 3)
  • Space admin on the phone — members and join requests, the space menu, its links and its pages
  • Moderation where it happens: a flagged post carries its reports, and a verdict settles it
  • Blog, bookmarks and push notifications
  • Light and dark theme that syncs to your Fluent Community colours
  • Multi-language — English, Spanish, Greek and Dutch included, add your own
  • Responsive layouts for phone, tablet and desktop
  • One installed app for your live site and its copies — admins switch to your staging site or a sandbox and back, with no second build

Modules

Extend it without touching the core

A clean module system lets you bolt on features without touching the core — so updates never overwrite your custom work. A growing library of modules is available, and you can build your own and share them with the community. See the modules.

Pricing & updates

Pay once for each site. Then it’s yours.

The lifetime license is exactly that: the complete Dev Bundle and setup dashboard, fully independent of us. Nothing phones home, nothing requires our servers — once you have it, it’s yours to build and ship with forever.

You pay for the license once for each site the app runs on: $399 a site for one or two sites, and $299 a site from three. Choose 1, 2, 3, 5 or 10 sites at checkout. One license key covers all of them.

Core updates are $50 a year for each site. They bring new features, Fluent Community compatibility, Expo and React Native upgrades, and changes for new phone systems and store rules. We ship often, so most people stay on. Updates are optional: cancel anytime and the renewal doesn’t happen. Cancelling never disables anything. Your license, your code and every app you’ve shipped keep working as they are.

Making apps for clients? Buy the Dev Bundle for as many sites as you have clients, run the builds from the dashboard, and charge your clients for managing their apps.

Want it done for you instead? The Managed plan — we handle builds, submissions and ongoing updates while the app still lives on your accounts. You can start there and buy the Dev Bundle later, when you want the source.

Siteslifetime license + first year of updatesAt checkout
1 site$399 license, then $50 / yr$449
2 sites$798 license, then $100 / yr$898
3 sites$897 license, then $150 / yr$1,047
5 sites$1,495 license, then $250 / yr$1,745
10 sites$2,990 license, then $500 / yr$3,490
Year two onwardonly if you want to stay current$50 / site / yr

0 Reviews

Loading reviews...

No reviews yet. Be the first to write a review!

Related Products

TBC - Web - Admin Columns

TBC - Web - Admin Columns

Fluent Community fields, stats, roles and user-meta as sortable, inline-editable columns on the Users list and WooCommerce Orders.

Price: $0.00
TBC - Web - Automations

TBC - Web - Automations

Eighteen Uncanny Automator triggers and actions for Fluent Community — DMs, notifications, follows, points, spaces, posts.

Price: $0.00
TBC - Web - User Switching

TBC - Web - User Switching

Puts the free User Switching plugin's buttons in the Fluent Community portal: Switch user on a member's profile, and Switch back in the header to return to your own account.

Price: $0.00
TBC -App - Better Messages Addon

TBC -App - Better Messages Addon

Hand the app’s messaging to Better Messages — threads, group chats, attachments, reactions, rooms — with push that deep-links to the conversation.

Price: $0.00
TBC App - Community App (Managed)

TBC App - Community App (Managed)

Your own branded community app on iOS and Android — built, published and kept up to date by us, under your accounts. You bring the content and branding; we handle every build and update.

From $33.00
TBC App - Youtube Widget

TBC App - Youtube Widget

Your latest uploads on the home screen, tapping through to playlists and videos — cached server-side so it never burns API quota.

Price: $29.00