NCW iOS CI: Forgejo Actions on-demand simulator build pipeline #8

Open
mohlec wants to merge 66 commits from ncw/main into main
Owner

Summary

Adds the NCW-specific iOS simulator build pipeline via Forgejo Actions, with full documentation.

What's included

  • .forgejo/workflows/build-ios.ymlworkflow_dispatch-only CI job that runs on the ncwmac macOS runner (macos label). Builds a Debug simulator .app, zips it, and publishes it as a rolling pre-release tag ios-sim-latest on the Forgejo releases page via the API.
  • docs/ncw-ios-ci.md — Full setup and operations guide covering: triggering builds, downloading and loading artifacts into the simulator, runner machine details, initial runner install from source (no upstream macOS binary), launchd service config, SSH deploy key setup, PATH quirks, and troubleshooting runbook.
  • CLAUDE.md — Added callout linking to the new CI doc alongside the existing iOS Simulator Setup guide.
  • ios/Podfile.lock, package-lock.json — Updated to reflect local pod install state and correct 2.40.0 package version.

Key design decisions

  • Manual-only triggers (workflow_dispatch) — the Mac is not always on; no push/PR auto-builds
  • Releases API instead of upload-artifact — the mirrored action at git.ncwcom.com/actions/upload-artifact is only reachable from the git server's loopback, not from the Mac runner over the network
  • Hardcoded PATH in workflow env — launchd does not inherit the interactive shell PATH, so Homebrew and rbenv shims must be listed explicitly
  • forgejo-runner built from source — no pre-built macOS arm64 binary is published upstream; compiled directly on the Mac

Test plan

  • End-to-end build triggered via workflow_dispatch, all 7 steps green
  • ios-sim-latest release created with .zip asset attached
  • .app loads in iOS Simulator via drag-and-drop
  • Runner survives Mac restart (launchd auto-start verified)
  • Runner shows Offline in Forgejo when Mac is asleep (expected behavior documented)
## Summary Adds the NCW-specific iOS simulator build pipeline via Forgejo Actions, with full documentation. ### What's included - `.forgejo/workflows/build-ios.yml` — `workflow_dispatch`-only CI job that runs on the `ncwmac` macOS runner (`macos` label). Builds a Debug simulator `.app`, zips it, and publishes it as a rolling pre-release tag `ios-sim-latest` on the Forgejo releases page via the API. - `docs/ncw-ios-ci.md` — Full setup and operations guide covering: triggering builds, downloading and loading artifacts into the simulator, runner machine details, initial runner install from source (no upstream macOS binary), launchd service config, SSH deploy key setup, PATH quirks, and troubleshooting runbook. - `CLAUDE.md` — Added callout linking to the new CI doc alongside the existing iOS Simulator Setup guide. - `ios/Podfile.lock`, `package-lock.json` — Updated to reflect local pod install state and correct 2.40.0 package version. ### Key design decisions - **Manual-only triggers** (`workflow_dispatch`) — the Mac is not always on; no push/PR auto-builds - **Releases API instead of `upload-artifact`** — the mirrored action at `git.ncwcom.com/actions/upload-artifact` is only reachable from the git server's loopback, not from the Mac runner over the network - **Hardcoded PATH in workflow env** — launchd does not inherit the interactive shell PATH, so Homebrew and rbenv shims must be listed explicitly - **forgejo-runner built from source** — no pre-built macOS arm64 binary is published upstream; compiled directly on the Mac ## Test plan - [x] End-to-end build triggered via workflow_dispatch, all 7 steps green - [x] `ios-sim-latest` release created with `.zip` asset attached - [x] `.app` loads in iOS Simulator via drag-and-drop - [x] Runner survives Mac restart (launchd auto-start verified) - [x] Runner shows Offline in Forgejo when Mac is asleep (expected behavior documented)
* MM-68274 - Adding watermark capability in mobile

* Resolving mobile comments

* Fix watermark overlay on Android

* Updating mobile

* Updating test and watermark

* Update watermark

* Update app/screens/home/channel_list/channel_list.test.tsx



* Update tests

---------


(cherry picked from commit fbf37dd515)

Co-authored-by: Asaad Mahmood <asaadmahmood@users.noreply.github.com>
Co-authored-by: Your Name <larkox@gmail.com>
* [MM-68255] Enable mobile support for Managed Categories

* Fix error handling

* More feedback

* Move function to correct file

* PR feedback

* Some changes

* Feedback

(cherry picked from commit daec2522f3)

Co-authored-by: Devin Binnie <52460000+devinbinnie@users.noreply.github.com>
Bump app build and version number (#9697) (#9698)
Some checks failed
build-android-release / test (push) Has been cancelled
build-android-release / build-and-deploy-android-release (push) Has been cancelled
build-ios-release / test (push) Has been cancelled
build-ios-release / build-and-deploy-ios-release (push) Has been cancelled
build-ios-release / build-ios-simulator (push) Has been cancelled
d39875a672
* Bump app build number to 744

* Bump app version number to 2.40.0

---------


(cherry picked from commit 57d57a48c6)

Co-authored-by: unified-ci-app[bot] <121569378+unified-ci-app[bot]@users.noreply.github.com>
Co-authored-by: Mattermost CI Bot <ci-bot@mattermost.com>
(cherry picked from commit 2bab054374)

Co-authored-by: Amy Blais <29708087+amyblais@users.noreply.github.com>
This reverts commit 8d68b1b485.

Per design feedback, the always-visible team sidebar approach is being
replaced by a bottom-sheet team menu accessible from the channel list
header (MM-68171 follow-up PR). Reverting ahead of code freeze; the
replacement lands separately.

(cherry picked from commit f119f49515)

Co-authored-by: JG Heithcock <jgheithcock@gmail.com>
* Revert "MM-68171 - Show team sidebar when other teams are available to join (#9658)"

This reverts commit 8d68b1b485.

Per design feedback, the always-visible team sidebar approach is being
replaced by a bottom-sheet team menu accessible from the channel list
header (MM-68171 follow-up PR). Reverting ahead of code freeze; the
replacement lands separately.

* MM-68171 - Add team menu bottom sheet to channel list header

Adds a tappable team name with a chevron-down affordance in the channel
list header that opens a bottom-sheet team menu with:

- "Join Another Team" - shown when the user can join other teams
- "Leave team" - shown when the user is a member of 2+ teams

Replaces the reverted #9658 always-visible-sidebar approach with the
bottom-sheet UX preferred by design. Single-team users who can join
other teams reach the join flow via the chevron menu instead of the
sidebar's "+" button.

* Update app/screens/home/channel_list/categories_list/header/header.tsx

Change from 18 to 24



---------


(cherry picked from commit 25b968d9b8)

Co-authored-by: JG Heithcock <jgheithcock@gmail.com>
Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>
Bump app build number to 747 (#9725)
Some checks failed
build-android-release / test (push) Has been cancelled
build-android-release / build-and-deploy-android-release (push) Has been cancelled
build-ios-release / test (push) Has been cancelled
build-ios-release / build-and-deploy-ios-release (push) Has been cancelled
build-ios-release / build-ios-simulator (push) Has been cancelled
2ed8db44ae
Co-authored-by: Mattermost CI Bot <ci-bot@mattermost.com>
Call dismissKeyboard() at the start of pushNotificationEntry (cold start)
and openNotification (background tap) before switching channel/thread so
iOS does not leave the post draft keyboard stuck over the destination.


(cherry picked from commit fab5456615)

Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>
Co-authored-by: Cursor Agent <agent@cursor.com>
Bump app build number to 749 (#9735)
Some checks failed
E2E Nightly Trigger / signal (push) Has been cancelled
github-release / test (push) Has been cancelled
github-release / build-ios-unsigned (push) Has been cancelled
github-release / build-android-unsigned (push) Has been cancelled
github-release / generate-consolidated-sbom (push) Has been cancelled
github-release / release (push) Has been cancelled
ci / lint-typecheck (push) Has been cancelled
ci / test (push) Has been cancelled
build-android-release / test (push) Has been cancelled
build-android-release / build-and-deploy-android-release (push) Has been cancelled
build-ios-release / test (push) Has been cancelled
build-ios-release / build-and-deploy-ios-release (push) Has been cancelled
build-ios-release / build-ios-simulator (push) Has been cancelled
ebf796a4da
Co-authored-by: Mattermost CI Bot <ci-bot@mattermost.com>
Add Forgejo Actions CI for Android APK build
Some checks failed
Build Slash Android APK / build-android (push) Failing after 12s
9ff0dfec80
Builds a debug APK on push to ncw/main using the native runner on CT#139.
Requires: JDK 21, Android SDK 35, NDK 27.1.12297006 (all installed on CT#139).
APK saved to /var/lib/forgejo/data/plugin-releases/, 5 builds retained.
Upload-artifact continue-on-error (same 443 workaround as slash-server).
On ncw-v* tag: creates a Forgejo release with the APK attached.

Refs slash-deploy #7 (Phase 5 Android)
NCW branding and dispatch/links WebView buttons
Some checks failed
Build Slash Android APK / build-android (push) Failing after 7s
9a33f3f026
- app.json, strings.xml: rename app to "Slash"
- build.gradle: applicationId com.ncwcom.slash, version 1.0.0 build 1
- config.json: DefaultServerUrl/Name chat.ncwcom.com, AutoSelectServerUrl true,
  AuthUrlScheme slash://, remove Mattermost analytics/review/onboarding
- header.tsx: add Dispatch Board (calendar) and Quick Links (apps) icon buttons
  to channel list header; opens plugin /mobile endpoint in Chrome Custom Tab
  via expo-web-browser with access_token auth; NCW purple toolbar

Closes slash-mobile #3, slash-mobile #4
CI: add --ignore-engines to npm install for Node 24 compat
Some checks failed
Build Slash Android APK / build-android (push) Failing after 7s
fe8ea48867
mattermost-mobile package.json engine field requires <=22, but
CT#139 runner has Node 24. Engine check is cosmetic — the build
itself works fine with --openssl-legacy-provider already in env.
CI: fix npm engine check for npm 11 (NPM_CONFIG_ENGINE_STRICT)
Some checks failed
Build Slash Android APK / build-android (push) Failing after 1m6s
148d788d12
--ignore-engines was removed in npm 11. Use NPM_CONFIG_ENGINE_STRICT=false
env var instead. Node 24 + npm 11 is the runner environment.
CI: skip solidarity preinstall check (emulator not needed for APK build)
Some checks failed
Build Slash Android APK / build-android (push) Failing after 7m5s
5dc5cec8bf
Add com.ncwcom.slash entry to google-services.json
All checks were successful
Build Slash Android APK / build-android (push) Successful in 17m57s
24396d16f9
Unblocks Gradle processDebugGoogleServices task. FCM push notifications
will need a proper Firebase project later (slash-deploy #8 followup).
Android: bundle JS into debug APK (bundleInDebug=true)
Some checks failed
Build Slash Android APK / build-android (push) Has been cancelled
8839643987
Debug builds were crashing with "Unable to load script" because the
JS bundle wasn't packaged — it expected a live Metro server. This
makes the sideloaded debug APK self-contained.
CI: publish rolling ncw-latest pre-release on every ncw/main build
All checks were successful
Build Slash Android APK / build-android (push) Successful in 15m6s
48258fe61d
Replaces ncw-latest tag+release on each push so techs always have a
direct APK download link without needing a manual tag.
Versioned releases (ncw-v*) still work as before.
Android: fix JS bundle packaging for sideloaded debug APK
All checks were successful
Build Slash Android APK / build-android (push) Successful in 15m19s
a5c9def1e4
bundleInDebug=true is a RN <=0.70 property — ignored by the new RNGP.
Set debuggableVariants=[] in the react{} block so RNGP bundles JS
for all variants including debug. Removes the Metro server requirement.
fix: fetch plugin mobile-token before opening Chrome Custom Tab
All checks were successful
Build Slash Android APK / build-android (push) Successful in 15m46s
4667d89230
access_token query param only works for Mattermost PATs, not regular
session tokens. New flow: fetch /api/v1/mobile-token with Authorization:
Bearer header first (works with session token), get back a plugin KV
UUID, then open /mobile?mobile_token=UUID in the browser.
fix(header): bigger NCW buttons (38px), center-align, shorten firstBox to 58%
All checks were successful
Build Slash Android APK / build-android (push) Successful in 20m9s
46625734fe
- NCW dispatch/links buttons: 28px -> 38px with slightly stronger background
- Icons: 16px -> 20px to match larger button
- buttonsRow: alignItems flex-start -> center so buttons sit centered vs the text
- firstBox: 72% -> 58% to give buttons room without clipping
Team display name shortened to NCW via DB (server-side, no app change needed)
feat(android): replace Mattermost icons with Slash/NCW branding
All checks were successful
Build Slash Android APK / build-android (push) Successful in 16m28s
28405f98e3
- ic_launcher: Slash logo (rounded rect) at all 5 densities (mdpi→xxxhdpi)
- ic_launcher_round: same source for circular launcher slots
- ic_launcher_foreground: transparent logo centered on adaptive icon canvas
- ic_launcher_background: NCW purple (#5C0044) solid background
- ic_notification: white silhouette for notification tray
feat(android): NCW branded splash screen
All checks were successful
Build Slash Android APK / build-android (push) Successful in 16m16s
8546dbde9e
- splash_background.png: solid NCW purple (#5C0044) at all densities, day + night
- splash.png: Slash logo (transparent bg) at all densities, day + night
- splashscreen_bg color: #FFFFFF -> #5C0044 (day), #090A0B -> #5C0044 (night)
fix(android): white icon background so Slash logo is visible
All checks were successful
Build Slash Android APK / build-android (push) Successful in 15m5s
fc0b56c668
fix(branding): align colors to NCW brand kit
All checks were successful
Build Slash Android APK / build-android (push) Successful in 15m49s
30286e352e
- Dispatch toolbar: #1e325c -> #353535 (NCW Grey)
- Links toolbar: #5C0044 -> #990066 (NCW Purple primary)
- Splash background: #5C0044 -> #353535 (NCW Grey), day + night
feat: ntfy SSE push notifications (no FCM/Google required)
Some checks failed
Build Slash Android APK / build-android (push) Failing after 6m13s
2bb3374190
- NtfyPushService: Android foreground service that maintains a persistent
  SSE connection to push.ncwcom.com and shows local notifications when
  messages arrive via ntfy. Survives app backgrounding/kill.
- NtfyBootReceiver: restarts the service after device reboot
- NtfyModule/NtfyPackage: RN bridge to get device UUID and start service
- push_notifications.ts: on Android, registers ntfy:UUID token with
  Mattermost instead of FCM. Mattermost -> push proxy -> ntfy -> SSE -> device.
- Token format: ntfy:{uuid} — Mattermost splits on : giving platform=ntfy
fix(android): build errors in ntfy integration
All checks were successful
Build Slash Android APK / build-android (push) Successful in 17m22s
a07ef65377
- MainApplication.kt: move NtfyPackage() into reactNativeHost.getPackages()
  instead of the ExpoReactHostFactory block (wrong conflict side was kept).
  Removes the duplicate reactHost declaration that caused the build failure.
- NtfyPushService.kt: fix notification icon R.drawable -> R.mipmap
  (ic_notification lives in mipmap-*, not drawable)
fix(ci): replace actions/checkout with inline git clone
Some checks failed
Build Slash Android APK / build-android (push) Failing after 1s
c295f823db
No external action deps — works without internet access.
DEFAULT_ACTIONS_URL = self is now active on git.ncwcom.com.
fix(ntfy): detect dead SSE connections and replay missed messages
All checks were successful
Build Slash Android APK / build-android (push) Successful in 17m13s
fc7b7c32cf
- readTimeout 75s (ntfy keepalive is 45s) so a silently-dropped TCP
  connection surfaces as SocketTimeoutException instead of blocking
  readLine() forever after Doze/network switches
- reconnect immediately on timeout instead of exponential backoff
- persist last event time and reconnect with ?since= so messages
  published while disconnected are replayed
feat(ntfy): clear notifications when channel is read elsewhere
All checks were successful
Build Slash Android APK / build-android (push) Successful in 18m38s
2ed0c42f96
Bridge now forwards Mattermost 'clear' pushes as mm-clear tagged ntfy
events with ch:{channel_id}. Notifications are posted with the channel
id as tag and cancelled when the matching clear arrives. The foreground
service notification (FG_NOTIF_ID) is never cancelled.
feat(ntfy): Slack-style conversation notifications; fix connection banner on reopen
All checks were successful
Build Slash Android APK / build-android (push) Successful in 21m54s
a6666488e7
- MessagingStyle: one card per channel (tag=channel_id, stable id),
  accumulating sender+message lines (max 8), grouped under a summary
  notification like Slack
- sender/channel names parsed from new sn:/cn: ntfy tags
- clear cancels the channel card + group summary when last one goes
- connection banner: reset initial-session guard on backgrounding -
  the ntfy FGS keeps the process alive, so every reopen was treated
  as a mid-session reconnect and flashed 'Connection restored'
Cancel legacy notifications on service start to clear stuck test alerts
All checks were successful
Build Slash Android APK / build-android (push) Successful in 16m55s
bfe8a33790
Fix Samsung badge count: FGS channel setShowBadge(false), setNumber on message notifications
Some checks failed
Build Slash Android APK / build-android (push) Has been cancelled
81cd205983
security: move ntfy credentials out of source into BuildConfig
All checks were successful
Build Slash Android APK / build-android (push) Successful in 16m58s
c682c22b9b
Hardcoded NTFY_USER/NTFY_PASS constants in NtfyPushService.kt were
visible in the public repo. They are now BuildConfig fields populated
at build time: from android/local.properties (gitignored) for local
builds, and from Forgejo repo secrets NTFY_USER/NTFY_PASS (passed via
-P flags to Gradle) in CI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix badge count not clearing: force FG channel recreate, setNumber on summary, fix cancel race
All checks were successful
Build Slash Android APK / build-android (push) Successful in 19m38s
3a1d900b1a
CHANNEL_SIDEBAR_GROUP_UNREADS already drives an UNREADS section header
that floats all unread DMs and channels above the normal category list.
Default was false (off); flip to true so all users get unread-first
behaviour without needing to set the preference manually.
Users who have explicitly set it off are unaffected.
feat: native Jitsi Meet integration for mobile (Phase 2)
Some checks failed
Build Slash Android APK / build-android (push) Failing after 4m25s
c7504f71a7
- Add JitsiMeeting component that renders custom_jitsi posts:
  JOIN MEETING button opens meeting_link in Chrome Custom Tab
- Wire custom_jitsi post type into post body/index.tsx
- Add Start Meeting (video-outline) button to channel list header;
  taps execute /meet in the current channel via executeCommand()
- currentChannelId added to header observable
Fix duplicate useIntl import in channel list header
Some checks failed
Build Slash Android APK / build-android (push) Failing after 4m14s
020bb4bd9e
Duplicate import introduced when adding /meet button in previous commit.
Caused SyntaxError at bundle time, failing the Android CI build.
ci: add iOS simulator build workflow (manual trigger only)
Some checks failed
Build Slash Android APK / build-android (push) Failing after 4m36s
22001fa1a4
ci: fix iOS workflow paths and URLs for Mac runner
Some checks failed
Build Slash Android APK / build-android (push) Failing after 2m49s
cfcfb360e1
ci: remove upload-artifact (unreachable mirror from Mac runner)
Some checks failed
Build Slash Android APK / build-android (push) Failing after 3m24s
81bf847f21
ci: bake Homebrew+rbenv into job PATH env so all steps find npm/ruby
Some checks failed
Build Slash Android APK / build-android (push) Failing after 2m36s
8c84dd0338
Fix duplicate intl declaration in channel list header
Some checks failed
Build Slash Android APK / build-android (push) Has been cancelled
9df4f79d98
const intl = useIntl() was added for onMeetPress but already existed
at line 151 in the same component scope. Remove the redundant declaration
and use the existing intl variable.
docs: add NCW iOS CI setup guide and Forgejo Actions workflow
All checks were successful
Build Slash Android APK / build-android (push) Successful in 15m21s
46ba348ff5
- Add docs/ncw-ios-ci.md covering the on-demand Forgejo Actions iOS
  simulator build pipeline: triggering builds, downloading artifacts,
  runner setup on ncwmac (launchd service, SSH deploy key, PATH config),
  initial runner install from source, and full troubleshooting reference
- Update CLAUDE.md callout block to reference the new CI doc alongside
  the existing iOS Simulator Setup guide
- Update Podfile.lock and package-lock.json to reflect local pod install
  and correct 2.40.0 package version
ci: skip Android build for iOS/docs-only pushes
Some checks failed
Build Slash Android APK / build-android (push) Has been cancelled
bd5cd485c2
Add paths-ignore to build-android.yml so pushes that only touch
ios/, build-ios.yml, docs/, or markdown files don't trigger a
20-minute Android build.
branding: rebrand iOS app from Mattermost to Slash
Some checks failed
Build Slash Android APK / build-android (push) Has been cancelled
5043dcb446
- Bundle IDs: com.mattermost.rnbeta -> com.ncwcom.slash
  - Share extension: com.ncwcom.slash.Share
  - NotificationService: com.ncwcom.slash.NotificationService
  - App group: group.com.ncwcom.slash
  - iCloud container: iCloud.com.ncwcom.slash
- App display name: 'Mattermost Beta' -> 'Slash'
- Deep link URL scheme: mattermost/mmauthbeta -> slash/slashauth
- Development team: UQ8HT4Q2XM (Mattermost) -> TBGN9CYYU5 (NCW)
- App icons: replaced Mattermost compass with NCW Purple (#990066)
  placeholder icons (S glyph) pending final NCW logo assets
- Gekidou logger subsystem: com.mattermost.Gekidou -> com.ncwcom.slash.Gekidou
- NotificationService queue label updated to com.ncwcom.slash.*
- fastlane/.env.ncw: NCW-specific build env template added
branding: rename display name Slash -> Slash Chat
All checks were successful
Build Slash Android APK / build-android (push) Successful in 16m12s
09601dece1
Fixes #9. Better discoverability on App Store/Play Store,
avoids collision with other 'Slash' apps.
branding: rebrand Android from Mattermost to Slash Chat
Some checks failed
Build Slash Android APK / build-android (push) Failing after 7m59s
715ad89dc3
- app_name: 'Slash' -> 'Slash Chat' (strings.xml)
- User-facing strings: 'Mattermost Server' -> 'Slash Chat Server'
- Deep link schemes: mattermost/mmauthbeta -> slash/slashauth
- namespace: com.mattermost.rnbeta -> com.ncwcom.slash (build.gradle)
- rootProject.name: Mattermost -> SlashChat (settings.gradle)
- Share activity/taskAffinity: com.mattermost.* -> com.ncwcom.slash.*
- Source dirs: com/mattermost/rnbeta/ -> com/ncwcom/slash/
  com/mattermost/helpers/ -> com/ncwcom/slash/helpers/
- Package declarations updated in all 40 .kt/.java files
- google-services.json: all package_name entries -> com.ncwcom.slash
- App icons: NCW Purple (#990066) placeholder in all mipmap densities
  (mdpi/hdpi/xhdpi/xxhdpi/xxxhdpi, regular + round)

Fixes #9 (Android items). Third-party library imports (com.mattermost.turbolog,
com.mattermost.rnutils etc) retained as-is — those are upstream library packages.
fix(android): update static imports in CustomPushNotificationHelper to com.ncwcom.slash.helpers
All checks were successful
Build Slash Android APK / build-android (push) Successful in 15m46s
fef426e508
com.mattermost.helpers.database_extension is our own renamed package, not an
external library — static imports were missed in the branding rename.
branding: rename user-facing Mattermost strings to Slash Chat
Some checks failed
Build Slash Android APK / build-android (push) Has been cancelled
be3ac165bd
- managed_app.ts: biometric/passcode prompts, MDM vendor name
- global_event_handler.ts: server upgrade error message

Pre-existing TS errors (7) in jitsi/push_notifications unrelated to this change.
branding: replace remaining user-facing Mattermost strings with Slash Chat
All checks were successful
Build Slash Android APK / build-android (push) Successful in 13m24s
00ae48d133
- message_bar.tsx: microphone permission prompt
- selection_team_bar.tsx: invite link share message
- review_app/index.tsx: app store review prompt
- utils/alerts/index.ts: biometric/screen-lock prompts + securedBy fallback
- about/learn_more.tsx: community link label (URL already points to chat.ncwcom.com)
branding: update app.json name/displayName to Slash Chat
All checks were successful
Build Slash Android APK / build-android (push) Successful in 15m59s
5f4def83fb
fix(fastlane): support IOS_API_KEY_FILEPATH for app_store_connect_api_key
All checks were successful
Build Slash Android APK / build-android (push) Successful in 14m44s
23b7c43cbc
Upstream get_apple_api_key() required the .p8 key content inlined via
IOS_API_KEY env var, which meant materializing a live private key as
plaintext env/scratch-file content. Added a preferred path that takes
IOS_API_KEY_ID + IOS_API_KEY_ISSUER_ID + IOS_API_KEY_FILEPATH and passes
the file path straight to app_store_connect_api_key, matching fastlane's
own recommended pattern. Falls back to the original inline-content path
if the filepath vars aren't set (keeps upstream CI unaffected).
PRODUCT_NAME controls the internal Xcode module name (used for the
auto-generated Swift bridging header, e.g. Mattermost-Swift.h), not
the user-facing app name. Renaming it to 'Slash' broke the build since
AppDelegate.mm still imports "Mattermost-Swift.h" directly. Reverted -
CFBundleDisplayName already correctly shows 'Slash Chat' everywhere a
user or the App Store would see it; PRODUCT_NAME is invisible plumbing.

Also:
- Added ncw_testflight lane: match certs + build_ios + submit_to_testflight,
  skipping the upstream build lane's configure/replace_assets/Sentry
  steps since NCW branding is already committed directly to source.
- Fixed setup_code_signing's MattermostShare detection to also match
  our renamed '.Share' bundle ID suffix.
- get_apple_api_key now supports IOS_API_KEY_FILEPATH (references an
  existing .p8 by path) as well as the original inline IOS_API_KEY
  content var, avoiding materializing the private key as plaintext.
- Confirmed CI=true is required to trigger setup_ci's temporary
  keychain - without it, codesign against the login keychain over SSH
  fails with errSecInternalComponent (no GUI session to authorize
  private key access). The .env.ncw / workflow both now rely on this.
ci(ios): add TestFlight workflow and document CI keychain requirement
All checks were successful
Build Slash Android APK / build-android (push) Successful in 15m47s
0168843de2
- New .forgejo/workflows/testflight-ios.yml: workflow_dispatch-only,
  builds signed Release archive via fastlane's ncw_testflight lane,
  uploads to TestFlight. Sets CI=true at job level, which is required
  for fastlane's setup_ci to create a temporary unlocked keychain -
  without it, codesigning over SSH fails with errSecInternalComponent
  since there's no GUI session to authorize private key access.
- fastlane/.env.ncw: filled in App Store Connect API key ID/issuer ID
  (not secrets - identifiers only; MATCH_PASSWORD/MATCH_KEYCHAIN_PASSWORD
  stay in the gitignored ~/.ncw-secrets/fastlane-secrets.env on ncwmac)
- docs/ncw-ios-ci.md: documented the TestFlight pipeline, the CI=true
  keychain gotcha, and the PRODUCT_NAME vs CFBundleDisplayName distinction
- .gitignore: exclude fastlane/.env.ncw.secrets (legacy path, secrets now
  live outside the repo checkout entirely at ~/.ncw-secrets/)
Root cause: the group summary notification was only cancelled via the
mm-clear path (messages read in-app). When the user swiped cards away
by hand, no deleteIntent existed, so an empty summary survived and
Samsung renders that as a perpetual '1 new message'.

- New NtfyDismissReceiver: on swipe-dismiss, prunes that channel's
  conversation history and cancels the summary when no cards remain
- Conversation cards + summary now carry a deleteIntent
- Summary is only posted at 2+ active conversations (a lone summary
  is exactly the phantom-notification shape); mm-clear path aligned
- Conversation history moved to a static holder so the receiver can
  prune it without binding the service
The foreground-service channel was created before setShowBadge(false)
existed on some devices (e.g. Samsung). The delete-and-recreate trick
never worked: Android restores a deleted channel's settings when it is
recreated under the same ID. Because the FGS notification is always
present, those devices show a permanent '1' badge on the app icon.

- FG channel renamed ntfy_fg_service -> ntfy_fg_service_v2 (fresh
  settings, showBadge=false actually applies); legacy channel deleted
- FGS notification additionally sets BADGE_ICON_NONE, number=0, silent
Reviewed-on: #12
Measured on the last full run (checkout->APK, PR #11 build): 16 min total,
but only ~10m43s was real build work. Breakdown:
  - checkout + npm install: ~1 min
  - gradle configure: ~2 min
  - gradle assembleDebug: 9m37s
  - upload-artifact@v3: ~5.5 min then fails (HTTP 500), masked by
    continue-on-error — pure waste, the APK is already saved locally
    and attached to the ncw-latest release two steps earlier

Changes:
  - Remove the upload-artifact step entirely (redundant + failing)
  - --max-workers 2 -> 8 (runner has 14 cores, was using a fraction)
  - -Xmx3g -> -Xmx6g (runner has 16GB, headroom was unused)
  - Enable org.gradle.caching + org.gradle.parallel in gradle.properties:
    runner is a persistent native host with a shared GRADLE_USER_HOME
    (already 6.6GB, downloads correctly persisting since Jun 9), so
    cached task outputs are valid inputs across builds. Gradle
    invalidates cache entries by task-input hash, not wall-clock age,
    so this does not reintroduce a stale-cache class of bug.
Reviewed-on: #13
fix(mobile): fix login broken on fresh install by push notification permission prompt
All checks were successful
Build Slash Android APK / build-android (push) Successful in 8m29s
f3764306e0
Root cause: the Server screen's componentDidAppear listener calls
NetworkManager.invalidateClient(url) to clean up if a user manually backs
out of a connection attempt. But useDidMount also unconditionally calls
PushNotifications.registerIfNeeded(), which on a fresh install triggers
the native iOS 'Allow Notifications' permission prompt. That system alert
causes a foreground/background transition which re-fires componentDidAppear
on the still-mounted Server screen sitting underneath the Login screen -
deleting the just-created network client while the user is still typing
their password.

By the time the user taps Log In, NetworkManager.getClient(serverUrl)
throws '<url> client not found' since the client was invalidated out from
under them. The request never reaches the network layer, matching
confirmed server-side logs showing zero incoming login attempts.

Fixed by tracking whether the user has already successfully connected
(hasConnectedRef) and skipping the invalidation once they have - the
original intent (clean up a stale client on manual back-navigation)
is preserved, but a post-connect re-fire caused by an unrelated OS-level
foreground event no longer tears down the active client.

Reproduced 100% of the time on fresh TestFlight installs by two
independent testers; root-caused via server-side log correlation
(zero incoming requests despite correct credentials) plus code trace.

Also fixes unrelated pre-existing TypeScript errors that were blocking
the pre-commit hook:
- app/init/push_notifications.ts: missing logWarning import
- types/api/posts.d.ts: custom_jitsi missing from PostType union (per
  the documented pattern in CLAUDE.md for adding custom post types)
- app/components/post_list/post/body/jitsi_meeting/index.tsx: use
  ensureString() instead of ?? '' for post.props fields, since
  NonNullable<unknown> resolves to {} not string
- header.test.tsx: add missing required currentChannelId to base test props
fix(fastlane): use absolute IPA path in ncw_testflight lane
All checks were successful
Build Slash Android APK / build-android (push) Successful in 7m49s
cebde38425
Relative paths ('./Slash_Chat.ipa') were unreliable across different
fastlane invocation contexts (Dir.pwd during action execution didn't
always match the repo root the shell was cd'd into). Use
File.expand_path relative to __dir__ (fastlane/) to derive an absolute
path that works regardless of invocation cwd.
TestFlight/App Store builds are signed with an Apple Distribution
provisioning profile, which requires aps-environment=production for
push notifications to work. With it set to 'development', APNs would
reject or silently drop pushes to any TestFlight-installed build,
regardless of whether the server-side delivery is correct.

Part of the iOS push notification fix alongside slash-push-bridge's
new APNs delivery implementation.
fix(android): guard startForeground() against Android 12+ background-restart crash
All checks were successful
Build Slash Android APK / build-android (push) Successful in 7m37s
19220f4ab3
Root cause of periodic background crashes reported on Samsung S22 (Matt,
NOC-style always-idle usage): startForeground() in onStartCommand() had
no error handling. NtfyPushService returns START_STICKY, so Android
auto-restarts it after being killed - Samsung's One UI is notably
aggressive about killing background services. On Android 12+, a
system-initiated restart of a foreground service outside an allowed
launch window throws ForegroundServiceStartNotAllowedException from
startForeground() itself. Left unguarded, this took down the whole app
process with zero diagnostic trail (no crash reporting is wired up in
this build - SentryEnabled is false).

Fixed by wrapping startForeground() in try/catch: on failure, log and
stop the service cleanly (stopSelf(), START_NOT_STICKY) instead of
crashing. The JS side already re-registers the service via
NtfyModule.startService() the next time the user opens the app, which
is a proper foreground-initiated launch and won't hit this restriction.

Investigated and ruled out: the two other recent notification-channel
commits (8a817e561 Samsung phantom-notification fix, 8decddb2e channel
rename to ntfy_fg_service_v2) - both read cleanly, no stale channel ID
references anywhere in the tree, and all their notify() calls are
already inside handleEvent()'s try/catch.
feat(dev): lightweight crash reporting via Mattermost webhook
All checks were successful
Build Slash Android APK / build-android (push) Successful in 7m55s
aeed7f68f2
DEV-ONLY tool for the current testing phase - not intended for
production, see inline warnings and PRD Phase 5 notes on where to
disable/remove before release.

No Sentry or other 3rd-party crash reporting was wired up, leaving zero
diagnostic trail for issues like the recent Android background-crash bug
(root-caused entirely from code review + user reports, no stack trace
available). This adds a minimal in-house alternative reusing existing
infra - no new servers, no new HAProxy routes:

- A private 'Crash Reports' Mattermost channel + incoming webhook receive
  reports (webhook already tested working: POST to
  https://chat.ncwcom.com/hooks/zgbotadhbintjps66d7erbqgte)
- app/utils/crash_report.ts: shared JS logic - ErrorUtils.setGlobalHandler
  hook for JS-level errors, posts device info + stack trace to the webhook
- Android (MainApplication.kt): Thread.setDefaultUncaughtExceptionHandler
  writes crash details to a local file synchronously (safe during process
  teardown) rather than attempting a network call mid-crash, then chains
  to the previous handler to preserve normal crash/termination behavior
- iOS (AppDelegate.mm): NSSetUncaughtExceptionHandler does the same for
  Objective-C/Swift exceptions. Hard native crashes (signals) are
  intentionally out of scope - correctly catching those requires a real
  signal handler, which is fragile to hand-roll and not worth it for a
  dev-only tool
- On next app launch, JS checks for a pending native crash file and
  uploads it if present (app/init/app.ts)
- New GLOBAL_IDENTIFIERS.CRASH_REPORTING_ENABLED WatermelonDB flag
  (app/queries+actions/app/global.ts), default on, absent record treated
  as enabled
- Settings toggle added to Advanced Settings screen, off switch persists
  immediately

Known limitation: webhook URL is embedded in the client config
(assets/base/config.json), same exposure model as the existing
SentryDsn*/RudderApiKey fields already in that file - extractable from
the compiled app, but only grants write access to one private channel.
Acceptable for the dev/test phase per user sign-off; flagged for removal
before production.
All checks were successful
Build Slash Android APK / build-android (push) Successful in 7m55s
This pull request has changes conflicting with the target branch.
  • .gitignore
  • android/app/build.gradle
  • android/app/src/main/AndroidManifest.xml
  • android/app/src/main/java/com/ncwcom/slash/MainApplication.kt
  • android/app/src/main/java/com/ncwcom/slash/helpers/push_notification/ErrorFormatting.kt
  • android/gradle.properties
  • android/settings.gradle
  • app.json
  • app/actions/local/session/index.ts
  • app/actions/remote/notifications.test.ts
  • app/components/post_list/post/body/index.tsx
  • app/components/team_sidebar/add_team/index.tsx
  • app/constants/screens.ts
  • app/init/app.ts
  • app/screens/home/channel_list/categories_list/categories_list.tsx
  • app/screens/home/channel_list/categories_list/header/header.test.tsx
  • app/screens/home/channel_list/categories_list/header/header.tsx
  • app/screens/home/channel_list/categories_list/header/index.ts
  • app/screens/home/channel_list/categories_list/header/team_menu/index.tsx
  • app/screens/home/channel_list/channel_list.test.tsx
  • app/screens/home/channel_list/channel_list.tsx
  • app/screens/index.tsx
  • app/screens/navigation.ts
  • app/screens/review_app/index.tsx
  • app/screens/server/index.tsx
  • app/screens/settings/about/learn_more.tsx
  • app/screens/settings/advanced/advanced.tsx
  • app/screens/watermark/index.test.tsx
  • app/screens/watermark/index.tsx
  • app/screens/watermark/watermark.tsx
  • app/store/ephemeral_store.ts
  • app/utils/team_menu.test.ts
  • app/utils/team_menu.ts
  • assets/base/i18n/en.json
  • index.ts
  • ios/Mattermost.xcodeproj/project.pbxproj
  • ios/Mattermost/AppDelegate.mm
  • ios/Mattermost/Info.plist
  • ios/MattermostShare/Info.plist
  • ios/NotificationService/Info.plist
  • ios/Podfile.lock
  • package-lock.json
  • package.json
  • patches/react-native-navigation+7.45.0.patch
  • types/api/properties.d.ts
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin ncw/main:ncw/main
git switch ncw/main

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff ncw/main
git switch ncw/main
git rebase main
git switch main
git merge --ff-only ncw/main
git switch ncw/main
git rebase main
git switch main
git merge --no-ff ncw/main
git switch main
git merge --squash ncw/main
git switch main
git merge --ff-only ncw/main
git switch main
git merge ncw/main
git push origin main
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
slash/slash-mobile!8
No description provided.