Compare commits

..

1 Commits

Author SHA1 Message Date
Element CI 5743f263b6 Prepare next release 2026-07-09 10:01:57 -07:00
31 changed files with 102 additions and 2160 deletions
@@ -11,11 +11,6 @@ permissions: {}
jobs: jobs:
tests: tests:
name: Tests name: Tests
# HealthChat: skip in this fork. It runs nightly Mon-Fri and fails at checkout, because
# permissions: {} leaves a metadata-only token that cannot read an INTERNAL repo. Gated rather
# than deleted so the file keeps rebasing cleanly. Run locally with
# `swift run tools ci accessibility-tests`.
if: github.repository == 'element-hq/element-x-ios'
runs-on: macos-26 runs-on: macos-26
timeout-minutes: 150 timeout-minutes: 150
-5
View File
@@ -20,11 +20,6 @@ permissions: {}
jobs: jobs:
tests: tests:
# HealthChat: skip in this fork. It only triggers on changes under compound-ios/, which this fork
# has never edited, so it would only ever fire on an upstream sync and go red at checkout —
# permissions: {} leaves a metadata-only token that cannot read an INTERNAL repo. Gated rather
# than deleted so the file keeps rebasing cleanly.
if: github.repository == 'element-hq/element-x-ios'
runs-on: macos-26 runs-on: macos-26
timeout-minutes: 15 timeout-minutes: 15
-5
View File
@@ -12,11 +12,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15 timeout-minutes: 15
permissions:
contents: read # HealthChat: this fork is INTERNAL, so a metadata-only token 404s on checkout. Upstream is public, hence permissions: {} there.
issues: read # HealthChat: Danger reads the PR via the issues API (a PR is an issue there); pull-requests alone 403s on GET /issues/{n}.
pull-requests: write # HealthChat: Danger posts its report comment and inline SwiftLint review comments (Dangerfile.swift:5 sets inline: true) with GITHUB_TOKEN.
concurrency: concurrency:
# Only allow a single run of this workflow on each branch, automatically cancelling older runs. # Only allow a single run of this workflow on each branch, automatically cancelling older runs.
group: ${{ format('danger-ci-{0}', github.ref) }} group: ${{ format('danger-ci-{0}', github.ref) }}
-7
View File
@@ -11,13 +11,6 @@ permissions: {}
jobs: jobs:
integration_tests: integration_tests:
name: Integration Tests name: Integration Tests
# HealthChat: skip in this fork. It runs nightly Mon-Fri and fails at checkout, and it is the one
# job that still calls out to Element's Codecov — its upload guard is true on schedule events, so
# it POSTs slug/branch/SHA every night with continue-on-error hiding the failure. Fixing it would
# need a real healthchat.ch test account and three new secrets, and would still not work:
# HealthChatAppSettingsHook sets allowOtherAccountProviders: false, hiding the change-server UI
# these tests drive. Gated rather than deleted so the file keeps rebasing cleanly.
if: github.repository == 'element-hq/element-x-ios'
runs-on: macos-26 runs-on: macos-26
timeout-minutes: 45 timeout-minutes: 45
-6
View File
@@ -15,12 +15,6 @@ permissions: {}
jobs: jobs:
tests: tests:
name: Tests name: Tests
# HealthChat: skip in this fork. It runs nightly Mon-Fri and fails at checkout, because
# permissions: {} leaves a metadata-only token that cannot read an INTERNAL repo. Fixing the
# permission would not help: the rebrand changed the start screen without re-recording any
# UITests snapshot, so it would fail on content instead. Gated rather than deleted so the file
# keeps rebasing cleanly. Run locally with `swift run tools ci ui-tests`.
if: github.repository == 'element-hq/element-x-ios'
runs-on: macos-26 runs-on: macos-26
timeout-minutes: 90 timeout-minutes: 90
strategy: strategy:
+1 -8
View File
@@ -17,14 +17,7 @@ jobs:
timeout-minutes: 45 timeout-minutes: 45
# Skip in forks # Skip in forks
# HealthChat: this job can never pass in this fork — it needs if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
# secrets.ELEMENT_BOT_TOKEN (Element's bot, absent here) and recursively
# checks out the private element-hq/element-ios-enterprise submodule, which
# Novaloop cannot obtain. The guard below only asks whether the PR comes
# FROM a fork, not whether this repo IS one, so it does not skip us. Gate on
# the upstream repo as post-release.yml does. Gated rather than deleted so
# the file keeps rebasing cleanly against upstream.
if: ${{ github.repository == 'element-hq/element-x-ios' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
concurrency: concurrency:
# Only allow a single run of this workflow on each branch, automatically cancelling older runs. # Only allow a single run of this workflow on each branch, automatically cancelling older runs.
+8 -68
View File
@@ -13,78 +13,24 @@ permissions: {}
jobs: jobs:
tests: tests:
name: Tests name: Tests
# HealthChat: GitHub-hosted macos-26 could not finish this job inside runs-on: macos-26
# timeout-minutes: 60 (two attempts: 51m42s, then killed at 60m44s), and macOS
# minutes bill at 10x. novaloop-mac-studio-001 is the org's Mac Studio, already
# used by the release pipeline; its labels are [self-hosted, macOS, ARM64].
runs-on: [self-hosted, macOS]
# Upstream's 60 is kept: with previews skipped (see Run tests) the build plus unit tests measured
# ~16 min here. A run approaching this budget is a signal, not normal — the earlier 60m timeouts
# were preview tests, not the build.
timeout-minutes: 60 timeout-minutes: 60
permissions:
contents: read # HealthChat: this fork is INTERNAL, so a metadata-only token 404s on checkout (and on git lfs pull). Upstream is public, hence permissions: {} there.
concurrency: concurrency:
# Only allow a single run of this workflow on each branch, automatically cancelling older runs. # Only allow a single run of this workflow on each branch, automatically cancelling older runs.
group: ${{ format('unit-tests-{0}', github.ref) }} group: ${{ format('unit-tests-{0}', github.ref) }}
cancel-in-progress: true cancel-in-progress: true
steps: steps:
# HealthChat: upstream passes persist-credentials: false here, which the
# composite forwards to actions/checkout — stripping the auth header before
# its separate "Git LFS Pull" step runs. Upstream can afford that because
# its repo is public and git lfs pull works anonymously; this fork is
# INTERNAL, so the LFS batch endpoint would 404. Keep the composite's own
# default (true) so the pull is authenticated with GITHUB_TOKEN.
- uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5 - uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
with:
persist-credentials: false
# HealthChat: verify only — never provision. Upstream's - name: Setup environment
# setup_github_actions_environment cannot run on this runner: it calls run: source ci_scripts/ci_common.sh && setup_github_actions_environment
# `sudo xcode-select -s /Applications/Xcode_26.5.0.app`, but the runner has no
# passwordless sudo and no such path (only /Applications/Xcode.app exists), and
# it calls `brew update && brew install ...` — novaloop-mac-studio-001 is the
# shared release machine, so a per-run `brew update` could upgrade xcodegen
# underneath the release pipeline. These tools are installed once by hand; CI
# only asserts they are present and fails loudly, naming the fix, if not.
- name: Verify environment
run: |
set -eu
missing=""
formulae=""
for tool in xcodegen swiftlint swiftformat xcbeautify xcresultparser pkl git-lfs; do
if ! command -v "$tool" >/dev/null 2>&1; then
missing="$missing $tool"
case "$tool" in
# Not in homebrew-core; only this tap ships it.
xcresultparser) formulae="$formulae a7ex/homebrew-formulae/xcresultparser" ;;
*) formulae="$formulae $tool" ;;
esac
fi
done
if [ -n "$missing" ]; then
echo "::error::Missing on self-hosted runner:${missing}. Install by hand on novaloop-mac-studio-001, then re-run: brew install${formulae}"
exit 1
fi
# The Xcode version is no longer pinned anywhere, so this is the only record
# of which Xcode built a given run.
xcodebuild -version
- name: Run tests - name: Run tests
# HealthChat: previews are skipped. PreviewTests renders a snapshot for every preview in the run: swift run -q tools ci unit-tests
# app and was the entire reason this job could not fit in an hour — it never once finished,
# on either runner. Unit tests alone take ~16 min here. Upstream skips them the same way in
# unit-tests-enterprise.yml. Cost: preview snapshot regressions are not caught in CI, so
# re-record and check them locally when changing UI.
env:
# HealthChat: VoiceMessageCacheTests cannot write its URLFileProtection.complete cache file on
# this runner's simulator (NSCocoaErrorDomain 513) — a runner-environment issue, not a code bug:
# the suite passes locally and upstream, and neither recreating+booting the device nor clearing
# state fixes it. Skip it here only, via -skip-testing. Drop this once the runner can write
# data-protected files again.
CI_SKIP_TESTS: VoiceMessageCacheTests
run: swift run -q tools ci unit-tests --skip-previews
- name: Archive artifacts - name: Archive artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
@@ -98,11 +44,7 @@ jobs:
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
# Skip if not successful and in forks # Skip if not successful and in forks
# HealthChat: the existing guard only asks whether the PR comes FROM a if: ${{ success() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
# fork, not whether this repo IS one, so it is true here and the step
# runs without a CODECOV_TOKEN — which fails the job via
# fail_ci_if_error. Gate on the upstream repo as post-release.yml does.
if: ${{ success() && github.repository == 'element-hq/element-x-ios' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with: with:
report_type: coverage report_type: coverage
@@ -114,9 +56,7 @@ jobs:
- name: Upload test results to Codecov - name: Upload test results to Codecov
# Skip if cancelled and in forks # Skip if cancelled and in forks
# HealthChat: gated on the upstream repo for the same reason as the if: ${{ !cancelled() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
# coverage step above — no CODECOV_TOKEN exists in this fork.
if: ${{ !cancelled() && github.repository == 'element-hq/element-x-ios' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
continue-on-error: true continue-on-error: true
with: with:
-2
View File
@@ -14,8 +14,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files. security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
contents: read # HealthChat: this fork is INTERNAL, so a metadata-only token 404s on checkout. Upstream is public, hence permissions: {} there.
actions: read # HealthChat: upload-sarif additionally needs this on private/internal repos; public upstream does not.
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+48
View File
@@ -1,3 +1,51 @@
## Changes in 26.07.3 (2026-07-09)
### What's Changed
✨ Features
* Content Scanning View by @Velin92 in https://github.com/element-hq/element-x-ios/pull/5831
* Content scanning view for reply preview by @Velin92 in https://github.com/element-hq/element-x-ios/pull/5842
* Scanning states for the media events timeline screen by @Velin92 in https://github.com/element-hq/element-x-ios/pull/5847
* Media Preview content scanning by @Velin92 in https://github.com/element-hq/element-x-ios/pull/5851
🙌 Improvements
* Don't keep re-prompting the user to migrate if they get spontaneously logged out. by @ara4n in https://github.com/element-hq/element-x-ios/pull/5844
* New location request permission strings by @Velin92 in https://github.com/element-hq/element-x-ios/pull/5854
🐛 Bugfixes
* Fix voice message recording crashes caused by an isolation assertion failure by @stefanceriu in https://github.com/element-hq/element-x-ios/pull/5820
* Stop reserving space for the non-existant icon background in ListRowLabel on iOS 26. by @pixlwave in https://github.com/element-hq/element-x-ios/pull/5822
* Fix for the media browser showing also images and videos in file mode by @Velin92 in https://github.com/element-hq/element-x-ios/pull/5853
🗣 Translations
* Translations update by @RiotRobot in https://github.com/element-hq/element-x-ios/pull/5845
🦻 Accessibility
* Fix AccessibilityTests hang on previews expecting expired transient state by @stefanceriu in https://github.com/element-hq/element-x-ios/pull/5821
* Remove the "unflip" of the timeline when in voice over mode by @Velin92 in https://github.com/element-hq/element-x-ios/pull/5835
🧱 Build
* Remove Hyperion from the Compound Inspector app. by @pixlwave in https://github.com/element-hq/element-x-ios/pull/5828
* Switch back to using the stable version of SwiftFormat. by @pixlwave in https://github.com/element-hq/element-x-ios/pull/5850
* Introduce a build setting to detect Nightly builds. by @pixlwave in https://github.com/element-hq/element-x-ios/pull/5852
🚧 In development 🚧
* Make LinkNewDeviceScreen fall back to the App Lock PIN code when there's no passcode. by @pixlwave in https://github.com/element-hq/element-x-ios/pull/5794
* Add the content scanner URL to the app settings by @Velin92 in https://github.com/element-hq/element-x-ios/pull/5823
* User Status preparation. by @pixlwave in https://github.com/element-hq/element-x-ios/pull/5825
* Content Scanner Service by @Velin92 in https://github.com/element-hq/element-x-ios/pull/5826
* Add message support to the new search tab by @stefanceriu in https://github.com/element-hq/element-x-ios/pull/5829
Others
* Bump the RustSDK to a new "apple-release" profile build distribution by @stefanceriu in https://github.com/element-hq/element-x-ios/pull/5834
* Update the readme to fix the unit tests shield and remove the SonarCloud ones by @stefanceriu in https://github.com/element-hq/element-x-ios/pull/5832
* Fix various project warnings by @stefanceriu in https://github.com/element-hq/element-x-ios/pull/5833
* Swift format enforce isEmpty rule by @Velin92 in https://github.com/element-hq/element-x-ios/pull/5843
* Update Project Dependencies by @renovate[bot] in https://github.com/element-hq/element-x-ios/pull/5818
**Full Changelog**: https://github.com/element-hq/element-x-ios/compare/release/26.07.0...release/26.07.3
## Changes in 26.07.1 (2026-07-03) ## Changes in 26.07.1 (2026-07-03)
### What's Changed ### What's Changed
+19 -43
View File
@@ -615,7 +615,6 @@
659E5B766F76FDEC1BF393A4 /* RoomDetailsEditScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E413F4CBD7BF0588F394A9DD /* RoomDetailsEditScreenViewModel.swift */; }; 659E5B766F76FDEC1BF393A4 /* RoomDetailsEditScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E413F4CBD7BF0588F394A9DD /* RoomDetailsEditScreenViewModel.swift */; };
661EF50C1F7D4B0BC8A7AAE3 /* EmoteRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44ABA63DBE7F76C58260B43B /* EmoteRoomTimelineView.swift */; }; 661EF50C1F7D4B0BC8A7AAE3 /* EmoteRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44ABA63DBE7F76C58260B43B /* EmoteRoomTimelineView.swift */; };
663E198678778F7426A9B27D /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FAFE1C2149E6AC8156ED2B /* Collection.swift */; }; 663E198678778F7426A9B27D /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FAFE1C2149E6AC8156ED2B /* Collection.swift */; };
665314B4CB31904F107D2D3B /* HealthChatAppSettingsHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1ECC44D40D05D3A6F0DE0F7 /* HealthChatAppSettingsHook.swift */; };
6681D6D3ADF69EBD2625F29A /* KnockedRoomProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E8F4D7D61B80EBD5CB92F8A /* KnockedRoomProxyMock.swift */; }; 6681D6D3ADF69EBD2625F29A /* KnockedRoomProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E8F4D7D61B80EBD5CB92F8A /* KnockedRoomProxyMock.swift */; };
66832DE7B5C2E861045265DC /* RoomSelectionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D879DC5515B1D42577F96C94 /* RoomSelectionScreen.swift */; }; 66832DE7B5C2E861045265DC /* RoomSelectionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D879DC5515B1D42577F96C94 /* RoomSelectionScreen.swift */; };
66C48B5B35C861C2089DBB12 /* ToolbarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510DA63AB273A68D659CEC95 /* ToolbarButton.swift */; }; 66C48B5B35C861C2089DBB12 /* ToolbarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510DA63AB273A68D659CEC95 /* ToolbarButton.swift */; };
@@ -670,7 +669,6 @@
6CBC544A7CC4DDDBDED4D0A6 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 4B1F71AC585827E6C416C15A /* AppIcon.icon */; }; 6CBC544A7CC4DDDBDED4D0A6 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 4B1F71AC585827E6C416C15A /* AppIcon.icon */; };
6CD61FAF03E8986523C2ABB8 /* StartChatScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3005886F00029F058DB62BE /* StartChatScreenCoordinator.swift */; }; 6CD61FAF03E8986523C2ABB8 /* StartChatScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3005886F00029F058DB62BE /* StartChatScreenCoordinator.swift */; };
6DC8E43BA04AC2AC4EB2EB97 /* AnalyticsPromptScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18486B87745B1811E7FBD3D2 /* AnalyticsPromptScreenModels.swift */; }; 6DC8E43BA04AC2AC4EB2EB97 /* AnalyticsPromptScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18486B87745B1811E7FBD3D2 /* AnalyticsPromptScreenModels.swift */; };
6DDA932A28F531791BDBFB08 /* HealthChatBrandColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A60C2712F5607406E8B1C80 /* HealthChatBrandColors.swift */; };
6E03A710799E6C65C0AB36BC /* TargetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D829FD8958376614504B18 /* TargetConfiguration.swift */; }; 6E03A710799E6C65C0AB36BC /* TargetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D829FD8958376614504B18 /* TargetConfiguration.swift */; };
6E0C68918BEC5D27A4830BA6 /* PlaybackSpeedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBB8E5436E1BE0D6814BB7D /* PlaybackSpeedButton.swift */; }; 6E0C68918BEC5D27A4830BA6 /* PlaybackSpeedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBB8E5436E1BE0D6814BB7D /* PlaybackSpeedButton.swift */; };
6E391F7F628D984AF44385D9 /* RoomAttachmentPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 128501375217576AF0FE3E92 /* RoomAttachmentPicker.swift */; }; 6E391F7F628D984AF44385D9 /* RoomAttachmentPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 128501375217576AF0FE3E92 /* RoomAttachmentPicker.swift */; };
@@ -935,7 +933,6 @@
95690DDD9D547D3D842ACBE3 /* AnalyticsSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */; }; 95690DDD9D547D3D842ACBE3 /* AnalyticsSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */; };
9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */; }; 9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */; };
95B690EA99A3385D4B8A593D /* NotificationToneManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94A6A57FE7E71F7605070AA /* NotificationToneManager.swift */; }; 95B690EA99A3385D4B8A593D /* NotificationToneManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94A6A57FE7E71F7605070AA /* NotificationToneManager.swift */; };
95C338B0F2101D8A8CA16A98 /* HealthChatCompoundHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = E09E2B1AB937F2D01815495F /* HealthChatCompoundHook.swift */; };
95E7B236F7116CACE05A6BC9 /* BlockedUsersScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A16D0F226B1819D017531647 /* BlockedUsersScreenCoordinator.swift */; }; 95E7B236F7116CACE05A6BC9 /* BlockedUsersScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A16D0F226B1819D017531647 /* BlockedUsersScreenCoordinator.swift */; };
9603EEF6DE980BB1D15D4707 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A3E8741D199CD1A37F4CBF /* UIView.swift */; }; 9603EEF6DE980BB1D15D4707 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A3E8741D199CD1A37F4CBF /* UIView.swift */; };
962A4F8AD6312804E2C6BB6E /* PhotoLibraryPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A232D9156D225BD9FD1D0C43 /* PhotoLibraryPicker.swift */; }; 962A4F8AD6312804E2C6BB6E /* PhotoLibraryPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A232D9156D225BD9FD1D0C43 /* PhotoLibraryPicker.swift */; };
@@ -1407,7 +1404,6 @@
E794AB6ABE1FF5AF0573FEA1 /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9332DFE9642F0A46ECA0497B /* BlurHashEncode.swift */; }; E794AB6ABE1FF5AF0573FEA1 /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9332DFE9642F0A46ECA0497B /* BlurHashEncode.swift */; };
E79D79CDAFE8BEBCC3AECA54 /* AppLockScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08283301736A6FE9D558B2CB /* AppLockScreenViewModelProtocol.swift */; }; E79D79CDAFE8BEBCC3AECA54 /* AppLockScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08283301736A6FE9D558B2CB /* AppLockScreenViewModelProtocol.swift */; };
E7DB506E77CED28D6FE6203D /* ManageAuthorizedSpacesScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8E7C716B8DC2F71E9AC439 /* ManageAuthorizedSpacesScreenModels.swift */; }; E7DB506E77CED28D6FE6203D /* ManageAuthorizedSpacesScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8E7C716B8DC2F71E9AC439 /* ManageAuthorizedSpacesScreenModels.swift */; };
E7F27870397639C204402434 /* HealthChatAppHooks.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B439012B010EC4C3DD24D0 /* HealthChatAppHooks.swift */; };
E81E8B7A8ADDB2A912A609F6 /* CallIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C2DB32B72EA4772E2C9E3B3 /* CallIntent.swift */; }; E81E8B7A8ADDB2A912A609F6 /* CallIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C2DB32B72EA4772E2C9E3B3 /* CallIntent.swift */; };
E82E13CC3EB923CCB8F8273C /* TimelineProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E543072DE58E751F028998 /* TimelineProxy.swift */; }; E82E13CC3EB923CCB8F8273C /* TimelineProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E543072DE58E751F028998 /* TimelineProxy.swift */; };
E84ADFE9696936C18C2424B5 /* SecureBackupScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A00BB9CD12CF6AC98D5485 /* SecureBackupScreen.swift */; }; E84ADFE9696936C18C2424B5 /* SecureBackupScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A00BB9CD12CF6AC98D5485 /* SecureBackupScreen.swift */; };
@@ -2345,7 +2341,6 @@
796CBD0C56FA0D3AEDAB255B /* SessionVerificationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenCoordinator.swift; sourceTree = "<group>"; }; 796CBD0C56FA0D3AEDAB255B /* SessionVerificationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenCoordinator.swift; sourceTree = "<group>"; };
79A106D76741914F82664959 /* StateStoreViewModelV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateStoreViewModelV2.swift; sourceTree = "<group>"; }; 79A106D76741914F82664959 /* StateStoreViewModelV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateStoreViewModelV2.swift; sourceTree = "<group>"; };
7A03E073077D92AA19C43DCF /* IdentityConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenCoordinator.swift; sourceTree = "<group>"; }; 7A03E073077D92AA19C43DCF /* IdentityConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenCoordinator.swift; sourceTree = "<group>"; };
7A60C2712F5607406E8B1C80 /* HealthChatBrandColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthChatBrandColors.swift; sourceTree = "<group>"; };
7A6D867A7FBB70C6EFDBCBC5 /* AccessibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityTests.swift; sourceTree = "<group>"; }; 7A6D867A7FBB70C6EFDBCBC5 /* AccessibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityTests.swift; sourceTree = "<group>"; };
7AAD8C633AA57948B34EDCF7 /* RoomChangeRolesScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenViewModelProtocol.swift; sourceTree = "<group>"; }; 7AAD8C633AA57948B34EDCF7 /* RoomChangeRolesScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenViewModelProtocol.swift; sourceTree = "<group>"; };
7AB74D407F0E413E7593B369 /* SpaceAddRoomsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceAddRoomsScreenViewModelTests.swift; sourceTree = "<group>"; }; 7AB74D407F0E413E7593B369 /* SpaceAddRoomsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceAddRoomsScreenViewModelTests.swift; sourceTree = "<group>"; };
@@ -2943,8 +2938,6 @@
DF3D25B3EDB283B5807EADCF /* ReadMarkerRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineItem.swift; sourceTree = "<group>"; }; DF3D25B3EDB283B5807EADCF /* ReadMarkerRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineItem.swift; sourceTree = "<group>"; };
DFFB0E7C6D8E190AFA0176DC /* uz */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uz; path = uz.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; DFFB0E7C6D8E190AFA0176DC /* uz */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uz; path = uz.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
E06AAD6D9D3F5833E7A5A2F9 /* RoomListFilterModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFilterModels.swift; sourceTree = "<group>"; }; E06AAD6D9D3F5833E7A5A2F9 /* RoomListFilterModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFilterModels.swift; sourceTree = "<group>"; };
E09E2B1AB937F2D01815495F /* HealthChatCompoundHook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthChatCompoundHook.swift; sourceTree = "<group>"; };
E0B439012B010EC4C3DD24D0 /* HealthChatAppHooks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthChatAppHooks.swift; sourceTree = "<group>"; };
E0FCA0957FAA0E15A9F5579D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Untranslated.stringsdict; sourceTree = "<group>"; }; E0FCA0957FAA0E15A9F5579D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Untranslated.stringsdict; sourceTree = "<group>"; };
E0FF9CB3EFA753277291F609 /* EncryptionResetScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetScreenCoordinator.swift; sourceTree = "<group>"; }; E0FF9CB3EFA753277291F609 /* EncryptionResetScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetScreenCoordinator.swift; sourceTree = "<group>"; };
E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileListRow.swift; sourceTree = "<group>"; }; E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileListRow.swift; sourceTree = "<group>"; };
@@ -2953,7 +2946,6 @@
E1714DD78BFE27ADD40A6F89 /* LinkNewDeviceScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceScreenViewModelTests.swift; sourceTree = "<group>"; }; E1714DD78BFE27ADD40A6F89 /* LinkNewDeviceScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceScreenViewModelTests.swift; sourceTree = "<group>"; };
E1A5FEF17ED7E6176D922D4F /* RoomDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreen.swift; sourceTree = "<group>"; }; E1A5FEF17ED7E6176D922D4F /* RoomDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreen.swift; sourceTree = "<group>"; };
E1E0B4A34E69BD2132BEC521 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = "<group>"; }; E1E0B4A34E69BD2132BEC521 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = "<group>"; };
E1ECC44D40D05D3A6F0DE0F7 /* HealthChatAppSettingsHook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthChatAppSettingsHook.swift; sourceTree = "<group>"; };
E1ED17433ADC77287F8904F9 /* CallNotificationRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallNotificationRoomTimelineItem.swift; sourceTree = "<group>"; }; E1ED17433ADC77287F8904F9 /* CallNotificationRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallNotificationRoomTimelineItem.swift; sourceTree = "<group>"; };
E20403084A320D588ACED200 /* ReportRoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportRoomScreenViewModelProtocol.swift; sourceTree = "<group>"; }; E20403084A320D588ACED200 /* ReportRoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportRoomScreenViewModelProtocol.swift; sourceTree = "<group>"; };
E2520C4F33AA0C061D209C28 /* RoomMembersListScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenTests.swift; sourceTree = "<group>"; }; E2520C4F33AA0C061D209C28 /* RoomMembersListScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenTests.swift; sourceTree = "<group>"; };
@@ -3596,7 +3588,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B6E4AB573FAEBB7B853DD04C /* AppHooks.swift */, B6E4AB573FAEBB7B853DD04C /* AppHooks.swift */,
801BA878EE879E01E82F3A5F /* HealthChat */,
2ED530A4DD98266D31E5D8E1 /* Hooks */, 2ED530A4DD98266D31E5D8E1 /* Hooks */,
); );
path = AppHooks; path = AppHooks;
@@ -5375,17 +5366,6 @@
path = View; path = View;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
801BA878EE879E01E82F3A5F /* HealthChat */ = {
isa = PBXGroup;
children = (
E0B439012B010EC4C3DD24D0 /* HealthChatAppHooks.swift */,
E1ECC44D40D05D3A6F0DE0F7 /* HealthChatAppSettingsHook.swift */,
7A60C2712F5607406E8B1C80 /* HealthChatBrandColors.swift */,
E09E2B1AB937F2D01815495F /* HealthChatCompoundHook.swift */,
);
path = HealthChat;
sourceTree = "<group>";
};
8039515BAA53B7C3275AC64A /* Client */ = { 8039515BAA53B7C3275AC64A /* Client */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -7500,34 +7480,34 @@
ORGANIZATIONNAME = Element; ORGANIZATIONNAME = Element;
TargetAttributes = { TargetAttributes = {
0E28CD62691FDBC63147D5E3 = { 0E28CD62691FDBC63147D5E3 = {
DevelopmentTeam = 6G37K72W82; DevelopmentTeam = 7J4U792NQT;
TestTargetID = C0FAEB81CFD9776CD78CE489; TestTargetID = C0FAEB81CFD9776CD78CE489;
}; };
19F0C845D67E9BEA4BE7133E = { 19F0C845D67E9BEA4BE7133E = {
DevelopmentTeam = "$(DEVELOPMENT_TEAM)"; DevelopmentTeam = "$(DEVELOPMENT_TEAM)";
}; };
32C23C8D224D46EFE62AFAD0 = { 32C23C8D224D46EFE62AFAD0 = {
DevelopmentTeam = 6G37K72W82; DevelopmentTeam = 7J4U792NQT;
}; };
676A87694F0F247B2AF1A142 = { 676A87694F0F247B2AF1A142 = {
DevelopmentTeam = 6G37K72W82; DevelopmentTeam = 7J4U792NQT;
}; };
7A17BE29BAC81ADBAC6349D9 = { 7A17BE29BAC81ADBAC6349D9 = {
DevelopmentTeam = 6G37K72W82; DevelopmentTeam = 7J4U792NQT;
}; };
C0C687DE1D270F9895FEE186 = { C0C687DE1D270F9895FEE186 = {
DevelopmentTeam = 6G37K72W82; DevelopmentTeam = 7J4U792NQT;
TestTargetID = C0FAEB81CFD9776CD78CE489; TestTargetID = C0FAEB81CFD9776CD78CE489;
}; };
C0FAEB81CFD9776CD78CE489 = { C0FAEB81CFD9776CD78CE489 = {
DevelopmentTeam = "$(DEVELOPMENT_TEAM)"; DevelopmentTeam = "$(DEVELOPMENT_TEAM)";
}; };
D3DB351B7FBE0F49649171FC = { D3DB351B7FBE0F49649171FC = {
DevelopmentTeam = 6G37K72W82; DevelopmentTeam = 7J4U792NQT;
TestTargetID = C0FAEB81CFD9776CD78CE489; TestTargetID = C0FAEB81CFD9776CD78CE489;
}; };
F8E276FD6DC43EADB85241BC = { F8E276FD6DC43EADB85241BC = {
DevelopmentTeam = 6G37K72W82; DevelopmentTeam = 7J4U792NQT;
}; };
FEB53A5BC378C913769656D8 = { FEB53A5BC378C913769656D8 = {
DevelopmentTeam = "$(DEVELOPMENT_TEAM)"; DevelopmentTeam = "$(DEVELOPMENT_TEAM)";
@@ -8530,10 +8510,6 @@
4295E5F850897710A51AE114 /* GeoURI.swift in Sources */, 4295E5F850897710A51AE114 /* GeoURI.swift in Sources */,
F3C9CAD26FD4D7D6EBACF501 /* HTMLFixtures.swift in Sources */, F3C9CAD26FD4D7D6EBACF501 /* HTMLFixtures.swift in Sources */,
E8C4D9F93F0DCED211D5F187 /* HTMLParserStyle.swift in Sources */, E8C4D9F93F0DCED211D5F187 /* HTMLParserStyle.swift in Sources */,
E7F27870397639C204402434 /* HealthChatAppHooks.swift in Sources */,
665314B4CB31904F107D2D3B /* HealthChatAppSettingsHook.swift in Sources */,
6DDA932A28F531791BDBFB08 /* HealthChatBrandColors.swift in Sources */,
95C338B0F2101D8A8CA16A98 /* HealthChatCompoundHook.swift in Sources */,
0C1E537A49ABB386F7554D4A /* HighlightedTimelineItemModifier.swift in Sources */, 0C1E537A49ABB386F7554D4A /* HighlightedTimelineItemModifier.swift in Sources */,
964B9D2EC38C488C360CE0C9 /* HomeScreen.swift in Sources */, 964B9D2EC38C488C360CE0C9 /* HomeScreen.swift in Sources */,
62C5876C4254C58C2086F0DE /* HomeScreenContent.swift in Sources */, 62C5876C4254C58C2086F0DE /* HomeScreenContent.swift in Sources */,
@@ -9857,11 +9833,11 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
APP_DISPLAY_NAME = HealthChat; APP_DISPLAY_NAME = "Element X";
APP_GROUP_IDENTIFIER = group.ch.healthchat; APP_GROUP_IDENTIFIER = group.io.element;
APP_NAME = ElementX; APP_NAME = ElementX;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = "colors/accent-color"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = "colors/accent-color";
BASE_BUNDLE_IDENTIFIER = ch.healthchat.element; BASE_BUNDLE_IDENTIFIER = io.element.elementx;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@@ -9899,7 +9875,7 @@
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6G37K72W82; DEVELOPMENT_TEAM = 7J4U792NQT;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -9915,10 +9891,10 @@
IS_NIGHTLY_BUILD = NO; IS_NIGHTLY_BUILD = NO;
KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)"; KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)";
MACOSX_DEPLOYMENT_TARGET = 14.5; MACOSX_DEPLOYMENT_TARGET = 14.5;
MARKETING_VERSION = 26.07.3; MARKETING_VERSION = 26.07.4;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCTION_APP_NAME = HealthChat; PRODUCTION_APP_NAME = Element;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
@@ -9932,11 +9908,11 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
APP_DISPLAY_NAME = HealthChat; APP_DISPLAY_NAME = "Element X";
APP_GROUP_IDENTIFIER = group.ch.healthchat; APP_GROUP_IDENTIFIER = group.io.element;
APP_NAME = ElementX; APP_NAME = ElementX;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = "colors/accent-color"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = "colors/accent-color";
BASE_BUNDLE_IDENTIFIER = ch.healthchat.element; BASE_BUNDLE_IDENTIFIER = io.element.elementx;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@@ -9974,7 +9950,7 @@
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 6G37K72W82; DEVELOPMENT_TEAM = 7J4U792NQT;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
@@ -9996,11 +9972,11 @@
IS_NIGHTLY_BUILD = NO; IS_NIGHTLY_BUILD = NO;
KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)"; KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)";
MACOSX_DEPLOYMENT_TARGET = 14.5; MACOSX_DEPLOYMENT_TARGET = 14.5;
MARKETING_VERSION = 26.07.3; MARKETING_VERSION = 26.07.4;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
PRODUCTION_APP_NAME = HealthChat; PRODUCTION_APP_NAME = Element;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 265 KiB

After

Width:  |  Height:  |  Size: 244 KiB

@@ -1,23 +1,15 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "app-logo.png", "filename" : "app-logo.pdf",
"idiom" : "universal", "idiom" : "universal"
"scale" : "1x"
},
{
"filename" : "app-logo@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "app-logo@3x.png",
"idiom" : "universal",
"scale" : "3x"
} }
], ],
"info" : { "info" : {
"author" : "xcode", "author" : "xcode",
"version" : 1 "version" : 1
},
"properties" : {
"preserves-vector-representation" : true
} }
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

@@ -1,5 +1,4 @@
"screen_home_tab_search" = "Search"; "screen_home_tab_search" = "Search";
"screen_onboarding_welcome_title_ios" = "Welcome to HealthChat";
"screen_search_empty_state_message" = "Search for chats and messages"; "screen_search_empty_state_message" = "Search for chats and messages";
"screen_search_empty_state_title" = "Start searching..."; "screen_search_empty_state_title" = "Start searching...";
"screen_search_no_results_message" = "There are no results for “%1$@.” Try a new search term."; "screen_search_no_results_message" = "There are no results for “%1$@.” Try a new search term.";
@@ -1,21 +0,0 @@
//
// Copyright 2026 Novaloop AG
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
/// Registers the HealthChat hooks.
///
/// `AppHooks` is a concrete type and `AppCoordinator` calls `setUp()` on it directly, so this method
/// wins over the empty `AppHooksProtocol` default via static dispatch — mirroring how Element's own
/// Enterprise fork provides its `setUp()`. This file is compiled only into the main app; the NSE and
/// Share Extension keep the empty default (they never drive authentication).
extension AppHooks {
func setUp() {
#if IS_MAIN_APP
registerAppSettingsHook(HealthChatAppSettingsHook())
registerCompoundHook(HealthChatCompoundHook())
#endif
}
}
@@ -1,40 +0,0 @@
//
// Copyright 2026 Novaloop AG
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import Foundation
/// Pins HealthChat's server, OIDC and push configuration by overriding `AppSettings` at startup.
///
/// - `accountProviders` / `allowOtherAccountProviders` lock every user to `healthchat.ch` (the server name;
/// `.well-known` delegates to `matrix.healthchat.ch`) and hide the change-server / create-account UI.
/// - `oAuthRedirectURL` uses the app's own URL scheme so the OIDC/MAS callback needs no associated domain.
/// - Legacy `element.io` help/encryption/copyright URLs are passed through unchanged for now.
struct HealthChatAppSettingsHook: AppSettingsHookProtocol {
func configure(_ appSettings: AppSettings) -> AppSettings {
appSettings.override(accountProviders: ["healthchat.ch"],
allowOtherAccountProviders: false,
hideBrandChrome: false,
pushGatewayBaseURL: "https://sygnal.healthchat.ch",
oAuthRedirectURL: "ch.healthchat.element:/oauth/callback",
websiteURL: "https://healthchat.ch",
logoURL: "https://app.healthchat.ch/themes/element/img/logos/element-logo.png",
copyrightURL: appSettings.copyrightURL,
acceptableUseURL: "https://www.healthchat.ch/datenschutz",
privacyURL: "https://www.healthchat.ch/datenschutz",
encryptionURL: appSettings.encryptionURL,
deviceVerificationURL: appSettings.deviceVerificationURL,
chatBackupDetailsURL: appSettings.chatBackupDetailsURL,
identityPinningViolationDetailsURL: appSettings.identityPinningViolationDetailsURL,
historySharingDetailsURL: appSettings.historySharingDetailsURL,
elementWebHosts: ["app.healthchat.ch"],
accountProvisioningHost: appSettings.accountProvisioningHost,
bugReportApplicationID: appSettings.bugReportApplicationID,
analyticsTermsURL: appSettings.analyticsTermsURL,
mapTilerConfiguration: AppSettings.bundledMapTilerConfiguration)
return appSettings
}
}
@@ -1,33 +0,0 @@
//
// Copyright 2026 Novaloop AG
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import UIKit
/// HealthChat brand palette, ported from the legacy Element iOS client.
/// These light/dark values are applied to Compound's accent token family by `HealthChatCompoundHook`.
///
/// **Note:** `nonisolated` is required. This target defaults to `MainActor` isolation, but UIKit resolves
/// the dynamic colour providers below on SwiftUI's render thread, which traps on an isolated closure.
nonisolated enum HealthChatBrandColors {
/// Primary brand blue — light `#1F4D85`, dark `#376A91`.
static let brand = dynamic(light: hex(0x1F4D85), dark: hex(0x376A91))
/// Pressed state — a slightly darker brand blue.
static let brandPressed = dynamic(light: hex(0x17385F), dark: hex(0x2A5171))
/// Subtle accent background / tint — light `#EBF0F7`, dark `#1A2D47`.
static let tint = dynamic(light: hex(0xEBF0F7), dark: hex(0x1A2D47))
private static func dynamic(light: UIColor, dark: UIColor) -> UIColor {
UIColor { $0.userInterfaceStyle == .dark ? dark : light }
}
private static func hex(_ value: Int, alpha: CGFloat = 1) -> UIColor {
UIColor(red: CGFloat((value >> 16) & 0xFF) / 255,
green: CGFloat((value >> 8) & 0xFF) / 255,
blue: CGFloat(value & 0xFF) / 255,
alpha: alpha)
}
}
@@ -1,84 +0,0 @@
//
// Copyright 2026 Novaloop AG
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import Compound
import SwiftUI
import UIKit
/// Recolours Compound's accent token family to the HealthChat brand.
/// Applied once at startup from `AppCoordinator` via `AppHooks.compoundHook`. Because the override
/// runs at app startup (not in previews), preview snapshots keep the default Compound colours.
struct HealthChatCompoundHook: CompoundHookProtocol {
func override(colors: CompoundColors, uiColors: CompoundUIColors) {
let brand = HealthChatBrandColors.brand
let tint = HealthChatBrandColors.tint
// Solid accent → brand blue (primary + send buttons, links, accent icons, badges, borders).
// NB: bgAccentRest is a SOLID fill (behind a white icon on the send button), not a subtle tint.
let solidColorTokens: [KeyPath<CompoundColorTokens, Color>] = [
\.bgActionPrimaryRest,
\.bgAccentRest,
\.iconAccentPrimary,
\.iconAccentTertiary,
\.textActionPrimary,
\.textActionAccent,
\.textLinkExternal,
\.borderInteractivePrimary,
\.textBadgeAccent
]
let solidUIColorTokens: [KeyPath<CompoundUIColorTokens, UIColor>] = [
\.bgActionPrimaryRest,
\.bgAccentRest,
\.iconAccentPrimary,
\.iconAccentTertiary,
\.textActionPrimary,
\.textActionAccent,
\.textLinkExternal,
\.borderInteractivePrimary,
\.textBadgeAccent
]
for keyPath in solidColorTokens {
colors.override(keyPath, with: Color(brand))
}
for keyPath in solidUIColorTokens {
uiColors.override(keyPath, with: brand)
}
// Primary button pressed state. The disabled state stays Compound's neutral grey.
colors.override(\.bgActionPrimaryPressed, with: Color(HealthChatBrandColors.brandPressed))
uiColors.override(\.bgActionPrimaryPressed, with: HealthChatBrandColors.brandPressed)
// Gradient "super"/action buttons → brand blue (SwiftUI-only concept).
let gradientTokens: [KeyPath<CompoundColorTokens, Color>] = [
\.gradientActionStop1,
\.gradientActionStop2,
\.gradientActionStop3,
\.gradientActionStop4
]
for keyPath in gradientTokens {
colors.override(keyPath, with: Color(brand))
}
// Subtle accent backgrounds (paired with accent-coloured foregrounds) → brand tint.
let tintColorTokens: [KeyPath<CompoundColorTokens, Color>] = [
\.bgAccentSelected,
\.bgBadgeAccent,
\.borderAccentSubtle
]
let tintUIColorTokens: [KeyPath<CompoundUIColorTokens, UIColor>] = [
\.bgAccentSelected,
\.bgBadgeAccent,
\.borderAccentSubtle
]
for keyPath in tintColorTokens {
colors.override(keyPath, with: Color(tint))
}
for keyPath in tintUIColorTokens {
uiColors.override(keyPath, with: tint)
}
}
}
@@ -12,8 +12,6 @@ import Foundation
internal nonisolated enum UntranslatedL10n { internal nonisolated enum UntranslatedL10n {
/// Search /// Search
internal static var screenHomeTabSearch: String { return UntranslatedL10n.tr("Untranslated", "screen_home_tab_search") } internal static var screenHomeTabSearch: String { return UntranslatedL10n.tr("Untranslated", "screen_home_tab_search") }
/// Welcome to HealthChat
internal static var screenOnboardingWelcomeTitleIos: String { return UntranslatedL10n.tr("Untranslated", "screen_onboarding_welcome_title_ios") }
/// Search for chats and messages /// Search for chats and messages
internal static var screenSearchEmptyStateMessage: String { return UntranslatedL10n.tr("Untranslated", "screen_search_empty_state_message") } internal static var screenSearchEmptyStateMessage: String { return UntranslatedL10n.tr("Untranslated", "screen_search_empty_state_message") }
/// Start searching... /// Start searching...
@@ -76,7 +76,7 @@ struct AuthenticationStartScreen: View {
if !context.viewState.hideBrandChrome { if !context.viewState.hideBrandChrome {
VStack(spacing: 8) { VStack(spacing: 8) {
Text(UntranslatedL10n.screenOnboardingWelcomeTitleIos) Text(L10n.screenOnboardingWelcomeTitle)
.font(.compound.headingLGBold) .font(.compound.headingLGBold)
.foregroundColor(.compound.textPrimary) .foregroundColor(.compound.textPrimary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
@@ -6,9 +6,13 @@
<string>development</string> <string>development</string>
<key>com.apple.developer.associated-domains</key> <key>com.apple.developer.associated-domains</key>
<array> <array>
<string>applinks:app.healthchat.ch</string> <string>applinks:element.io</string>
<string>applinks:app.element.io</string>
<string>applinks:staging.element.io</string>
<string>applinks:develop.element.io</string>
<string>applinks:mobile.element.io</string>
<string>applinks:matrix.to</string> <string>applinks:matrix.to</string>
<string>webcredentials:*.healthchat.ch</string> <string>webcredentials:*.element.io</string>
</array> </array>
<key>com.apple.developer.usernotifications.communication</key> <key>com.apple.developer.usernotifications.communication</key>
<true/> <true/>
@@ -17,12 +21,14 @@
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array>
<string>$(APP_GROUP_IDENTIFIER)</string> <string>$(APP_GROUP_IDENTIFIER)</string>
<string>$(CLASSIC_APP_GROUP_IDENTIFIER)</string>
</array> </array>
<key>com.apple.security.network.client</key> <key>com.apple.security.network.client</key>
<true/> <true/>
<key>keychain-access-groups</key> <key>keychain-access-groups</key>
<array> <array>
<string>$(KEYCHAIN_ACCESS_GROUP_IDENTIFIER)</string> <string>$(KEYCHAIN_ACCESS_GROUP_IDENTIFIER)</string>
<string>$(CLASSIC_APP_KEYCHAIN_ACCESS_GROUP_IDENTIFIER)</string>
</array> </array>
</dict> </dict>
</plist> </plist>
+1 -1
View File
@@ -30,7 +30,7 @@
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>$(APP_DISPLAY_NAME)</string> <string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
+9 -8
View File
@@ -48,9 +48,6 @@ targets:
UILaunchScreen: UILaunchScreen:
UIColorName: colors/background-color UIColorName: colors/background-color
CFBundleDisplayName: $(APP_DISPLAY_NAME) CFBundleDisplayName: $(APP_DISPLAY_NAME)
# HealthChat: without this CFBundleName defaults to $(PRODUCT_NAME) (ElementX, kept for the module
# name), which is what the web authentication session's sign-in alert shows — not CFBundleDisplayName.
CFBundleName: $(APP_DISPLAY_NAME)
CFBundleShortVersionString: $(MARKETING_VERSION) CFBundleShortVersionString: $(MARKETING_VERSION)
CFBundleVersion: $(CURRENT_PROJECT_VERSION) CFBundleVersion: $(CURRENT_PROJECT_VERSION)
CFBundleURLTypes: [ CFBundleURLTypes: [
@@ -118,19 +115,23 @@ targets:
properties: properties:
aps-environment: development aps-environment: development
com.apple.developer.associated-domains: com.apple.developer.associated-domains:
# HealthChat: app.healthchat.ch universal links + webcredentials (OAuth/autofill). matrix.to kept for permalinks. - applinks:element.io
- applinks:app.healthchat.ch - applinks:app.element.io
- applinks:staging.element.io
- applinks:develop.element.io
- applinks:mobile.element.io
- applinks:matrix.to - applinks:matrix.to
- webcredentials:*.healthchat.ch # - applinks:localhost?mode=developer # developer mode only works with https (but self-signed is fine).
- webcredentials:*.element.io
com.apple.developer.usernotifications.communication: true com.apple.developer.usernotifications.communication: true
com.apple.security.app-sandbox: true com.apple.security.app-sandbox: true
com.apple.security.application-groups: com.apple.security.application-groups:
# HealthChat: dropped $(CLASSIC_APP_GROUP_IDENTIFIER) (group.im.vector) — owned by Element's team, not provisionable here.
- $(APP_GROUP_IDENTIFIER) - $(APP_GROUP_IDENTIFIER)
- $(CLASSIC_APP_GROUP_IDENTIFIER)
com.apple.security.network.client: true com.apple.security.network.client: true
keychain-access-groups: keychain-access-groups:
# HealthChat: dropped $(CLASSIC_APP_KEYCHAIN_ACCESS_GROUP_IDENTIFIER) (im.vector.app.*) — not provisionable here.
- $(KEYCHAIN_ACCESS_GROUP_IDENTIFIER) - $(KEYCHAIN_ACCESS_GROUP_IDENTIFIER)
- $(CLASSIC_APP_KEYCHAIN_ACCESS_GROUP_IDENTIFIER)
settings: settings:
base: base:
-14
View File
@@ -55,16 +55,6 @@ struct RunTests: AsyncParsableCommand {
"com.apple.CoreSimulator.SimRuntime.iOS-\(runtime.replacingOccurrences(of: ".", with: "-"))" "com.apple.CoreSimulator.SimRuntime.iOS-\(runtime.replacingOccurrences(of: ".", with: "-"))"
} }
/// Test identifiers to pass to `-skip-testing`, read from the `CI_SKIP_TESTS` environment variable
/// (comma-separated, e.g. "VoiceMessageCacheTests"). Lets a workflow exclude a suite that only fails in
/// its environment without touching the shared test.
private var skippedTests: [String] {
(ProcessInfo.processInfo.environment["CI_SKIP_TESTS"] ?? "")
.split(separator: ",")
.map { $0.trimmingCharacters(in: .whitespaces) }
.filter { !$0.isEmpty }
}
func run() async throws { func run() async throws {
if let createName = createSimulatorName { if let createName = createSimulatorName {
guard let createType = createSimulatorType else { guard let createType = createSimulatorType else {
@@ -152,10 +142,6 @@ struct RunTests: AsyncParsableCommand {
command += " -only-testing:\(scheme)/\(testName)" command += " -only-testing:\(scheme)/\(testName)"
} }
for skippedTest in skippedTests {
command += " -skip-testing:\(scheme)/\(skippedTest)"
}
command += " | \(formatter)" command += " | \(formatter)"
try await CI.run(.path("/bin/zsh"), ["-cu", command]) try await CI.run(.path("/bin/zsh"), ["-cu", command])
-13
View File
@@ -1,13 +0,0 @@
# HealthChat product identity.
# Included permanently from project.yml (this fork always builds as HealthChat).
# Only build-setting scalars belong here — XcodeGen concatenates arrays on include-merge,
# so entitlement arrays (associated-domains, app-groups, keychain-groups) are edited directly
# in ElementX/SupportingFiles/target.yml instead.
# NB: APP_NAME (internal PRODUCT_NAME / module name) is intentionally left as ElementX so that
# `@testable import ElementX` and $(PRODUCT_MODULE_NAME) keep working.
settings:
APP_DISPLAY_NAME: HealthChat
PRODUCTION_APP_NAME: HealthChat
APP_GROUP_IDENTIFIER: group.ch.healthchat
BASE_BUNDLE_IDENTIFIER: ch.healthchat.element
DEVELOPMENT_TEAM: 6G37K72W82
File diff suppressed because it is too large Load Diff
@@ -1,470 +0,0 @@
# HealthChat (element-x-ios) — App Store release capability
**Date:** 2026-07-15
**Branch:** `release/healthchat`
**Status:** Design approved; revised after adversarial review (26 issues, 0 rejected)
## Context
`Novaloop-AG/element-x-ios` is the HealthChat rebrand of Element X. The rebrand landed in `01dc703b4`
(branding, fixed homeserver, variant seam). What it cannot do is ship: the repo has **no release machinery
of any kind** — no `fastlane/`, no `Gemfile`, and none of its 17 GitHub workflows build or upload anything.
The legacy fork `Novaloop-AG/element-ios` ships HealthChat to the App Store today via a proven pipeline
(last green run 2026-04-15, version 1.4.1). This design brings equivalent capability to element-x-ios and
captures the process in a skill.
Both apps use **the same bundle id** (`ch.healthchat.element`) and **the same Apple team** (`6G37K72W82`).
element-x therefore reaches the App Store as an **in-place update** of the existing HealthChat record. That
single fact drives most of this design — including the fact that the two apps can never coexist on a
device, which upstream's migration UX assumes they can.
### Why upstream's own release path is unavailable
element-hq ships element-x via Xcode Cloud. `ci_scripts/ci_post_clone.sh` branches on `$CI_WORKFLOW` and
calls `swift run pipeline update-foss-secrets` (`Tools/Sources/Commands/CI/ConfigureProduction.swift:8`).
`pipeline` lives only in the private `Enterprise` submodule (`.gitmodules` → `element-hq/element-ios-enterprise`;
`Enterprise/` is empty here and Novaloop cannot obtain it). Separately `ci_scripts/ci_post_xcodebuild.sh:13`
unconditionally uploads dSYMs to Element's Sentry org. Xcode Cloud is a dead end for this fork.
## Decisions
| # | Decision | Rationale |
|---|---|---|
| D1 | **In-place update** of the existing App Store record | Identity already aligns. `26.07.3` already clears the legacy's `1.4.1`, so no version bump is needed. |
| D2 | **Preserve** legacy sessions + E2EE keys | The migration code already ships; the fix is 3 scalars + 1 entitlement, all HealthChat's own provisionable ids. |
| D3 | **Port the legacy fastlane** (slim, purpose-built) | Xcode Cloud is blocked. The legacy pipeline is proven and reuses the identical team, cert and ASC key. |
| D4 | Migration fix ships **first, as its own PR** | It corrects a defect in `01dc703b4`. **Note: it is _not_ monotonic — see D9.** |
| D5 | Skill lives at `.agents/skills/releasing-healthchat/SKILL.md` | Reachable via the existing `.claude/skills -> ../.agents/skills` symlink. A new subdirectory cannot conflict on rebase. |
| D6 | Reuse the legacy's `HEALTHCHAT_*` secrets | Same team, cert and ASC key. **Reject fastlane match** — the legacy deliberately has none. |
| D7 | Keep the `healthchat-v*` tag namespace | Upstream owns `release/<version>` (`ReleaseToGithub.swift:83-85`) and `nightly/*`. |
| D8 | **Create** `HEALTHCHAT_CHANGES.md` as the release-notes source | element-x's `CHANGES.md` is owned by upstream's `release-to-github`. The file does **not exist** in this repo — it must be created, not carried over. |
| D9 | Fix the `.requiresBackup` dead-end with a **small upstream edit** | Accepts the first-ever edit to an upstream logic file. Required for D4 to be safe — see Part 1. |
---
## Part 1 — Classic migration fix (PR1, `pr-bugfix`)
### The defect
`01dc703b4` dropped `$(CLASSIC_APP_KEYCHAIN_ACCESS_GROUP_IDENTIFIER)` from the entitlements with the
rationale "not provisionable here". That was correct *about Element's identifiers* — but the fix is to
**repoint them at HealthChat's own**, not to remove them.
Left as-is, every existing user who updates is silently logged out and loses local encrypted history
unless they had key backup. `loadAccounts()` throws `invalidAppGroupIdentifier`, caught and logged only —
`AuthenticationService.swift:59-63`: *"No need to alert the user of the failure, just log it."*
### Why the fix is small
The plumbing already ships. `ElementX/SupportingFiles/target.yml:78-81` maps the values into `Info.plist`;
`InfoPlistReader.swift:28-31,129-143` reads them. `ClassicAppManager` reads exactly `accountIv`,
`accountAesKey`, `cryptoSDKStoreKey` (`ClassicAppManager.swift:31-35,61-68`) — and the legacy
`EncryptionKeyManager` writes exactly those three names, to service
`ch.healthchat.element.encryption-manager-service`, in access group
`$(AppIdentifierPrefix)ch.healthchat.element.keychain.shared`
(`element-ios/Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift:17,20-25,27`).
Only the scalars are wrong: `app.yml:11-13` points them at Element's `group.im.vector` / `im.vector.app.*`,
and `healthchat.yml` does not override them.
**Verified mechanics** (these were the design's load-bearing assumptions):
- **XcodeGen scalar precedence.** `healthchat.yml` is included after `app.yml` (`project.yml:71` vs `:61`)
and wins — proven by the already-generated pbxproj, where `APP_GROUP_IDENTIFIER = group.ch.healthchat`
and `BASE_BUNDLE_IDENTIFIER = ch.healthchat.element` beat app.yml's Element values.
- **No trailing-dot bug.** `$(AppIdentifierPrefix)` carries its own trailing dot, so the legacy resolves to
`6G37K72W82.ch.healthchat.element.keychain.shared`. `$(DEVELOPMENT_TEAM).ch.healthchat.element.keychain.shared`
is byte-equivalent and matches upstream's own convention (`app.yml:13`).
### The change
In `Variants/HealthChat/healthchat.yml`:
```yaml
CLASSIC_APP_GROUP_IDENTIFIER: group.ch.healthchat
CLASSIC_APP_KEYCHAIN_SERVICE_IDENTIFIER: ch.healthchat.element.encryption-manager-service
CLASSIC_APP_KEYCHAIN_ACCESS_GROUP_IDENTIFIER: "$(DEVELOPMENT_TEAM).ch.healthchat.element.keychain.shared"
```
In `ElementX/SupportingFiles/target.yml`, restore the classic keychain group to `keychain-access-groups`
and correct the misleading comment at `:132`:
```yaml
keychain-access-groups:
- $(KEYCHAIN_ACCESS_GROUP_IDENTIFIER)
# HealthChat: the legacy app's keychain group — ClassicAppManager reads the account + crypto store
# keys from here to migrate existing users. Same team prefix, so it is provisionable.
- $(CLASSIC_APP_KEYCHAIN_ACCESS_GROUP_IDENTIFIER)
```
**Do not** re-add `$(CLASSIC_APP_GROUP_IDENTIFIER)` to `com.apple.security.application-groups`: it resolves
to `group.ch.healthchat`, already the value of `$(APP_GROUP_IDENTIFIER)`, so it would duplicate the entry.
Correct the comment at `:128` to say this rather than implying the group was dropped as un-provisionable.
`CLASSIC_APP_DEEP_LINK_URL` needs no override — legacy HealthChat registers the `element` scheme
(`element-ios/Config/AppIdentifiers.xcconfig:22`) — but see the `.requiresBackup` fix below, which makes it
moot.
**Finally: run `xcodegen` and commit both `ElementX.xcodeproj/project.pbxproj` and
`ElementX/SupportingFiles/ElementX.entitlements`.** Both are tracked and not ignored. Without this the
checked-in entitlements still lack the classic group and anyone building the branch gets the un-fixed,
silently-failing behaviour.
### The `.requiresBackup` fix (D9) — why Part 1 is not monotonic
`availableSecrets` maps `.withoutBackup` / `.unusedBackup` → `.requiresBackup`
(`ClassicAppManager.swift:82-85`). `login(classicAppAccount:)` routes that to
`state.bindings.showClassicAppBackupInstructions`, whose sheet's only button fires `.openClassicApp` →
`appMediator.open(element://open)` (`AuthenticationStartScreenViewModel.swift:106`).
**That URL resolves to nothing.** element-x registers only `$(BASE_BUNDLE_IDENTIFIER)` and `matrix`
(`ElementX/SupportingFiles/target.yml:60-63`) — no `element` scheme — and the legacy app that *did*
register it has been replaced in place by D1's same-bundle-id update. Upstream's UX assumes Element Classic
(`im.vector.app`) and Element X (`io.element.elementx`) coexist as separate apps. HealthChat cannot.
So making `loadAccounts()` succeed switches on a UI branch that is worse than the status quo for
`.requiresBackup` users: today they see a working login screen; after the fix they see a modal urging them
to open an app that no longer exists. The sheet is dismissible (`@Environment(\.dismiss)`) and `otherOptions`
(`AuthenticationClassicAppAccountView.swift:106`) is an escape hatch, so it is not a hard lockout — but it
is a knowingly broken call-to-action.
**Fix:** in `AuthenticationStartScreenViewModel.login(classicAppAccount:)`, treat `.requiresBackup` the same
way `.unavailable` is already treated — skip the sheet and fall through to
`configureAccountProvider(account.serverName, loginHint: "mxid:\(account.userID)", fallbackHomeserverURL:)`.
Those users sign into their own account and re-verify the device, losing only history that by definition has
no backup.
This is the **first edit to an upstream logic file** in this fork, ending the rebrand's "no upstream logic
edits" property. It is ~3 lines in a method upstream rarely touches. Record it in the skill's divergence
section so a future rebase does not silently revert it.
### Precondition for D2 (verified, but confirm on device)
`AuthenticationStartScreenViewModel:46` gates the whole classic flow on
`appSettings.accountProviders.contains(serverName)`, where `serverName` is derived from the **MXID domain**
(`ClassicAppAccountManager.swift:107-111`), not the homeserver URL. HealthChat pins
`accountProviders: ["healthchat.ch"]`.
`curl https://healthchat.ch/.well-known/matrix/server` → `{"m.server": "matrix.healthchat.ch:8448"}`. This is
*server-name delegation*: the server is **named** `healthchat.ch` and merely reachable at
`matrix.healthchat.ch:8448`. Legacy MXIDs are therefore `@user:healthchat.ch`, the gate passes, and D2 holds.
Confirm against a real legacy MXID during device testing.
---
## Part 2 — Release pipeline (PR2, `pr-build`)
### Design note: this fork owns `fastlane/` outright
Upstream element-x-ios has **no `fastlane/` directory**. The legacy skill devotes a whole section to rebase
divergence in `fastlane/.env.default` and `fastlane/Fastfile` precisely because those are upstream-owned in
element-ios. Here that class of conflict does not exist — which is why this is a slim purpose-built Fastfile
(~120 lines), not a copy of Vector's ~600-line multi-variant one.
**Dropped:** cocoapods (element-x is SPM), diawi, sentry (HealthChat has no Sentry), and the `adhoc` /
`alpha` / `setup_app_variant` lanes. Siri-intents and broadcast-upload have no counterpart target here.
### Files
| File | Purpose |
|---|---|
| `fastlane/Fastfile` | Public `release_testflight` + private helpers. **No `before_all`, no Xcode pin** — see runner notes. |
| `fastlane/Appfile` | `apple_id ENV["APPLE_ID"]`, `team_id ENV["TEAM_ID"]` |
| `fastlane/.env.default` | Project/target/bundle-id/profile configuration (full key list below) |
| `fastlane/Pluginfile` | **`fastlane-plugin-xcodegen` only** |
| `Gemfile` + `Gemfile.lock` | `fastlane` + plugins. No cocoapods, no slather. **Commit the lock** — the workflow's gem cache keys on `hashFiles('**/Gemfile.lock')`; without it the key is constant and fastlane silently re-resolves between two runs of the same tag. |
| `HEALTHCHAT_CHANGES.md` | **New file** (D8). Seed with a `## Changes in 26.07.3 (<date>)` section matching the awk pattern. |
| `.gitignore` | Add `out/` and **`provisioning_profiles/`** (signing material — security-relevant). `build` (`:35`) and `fastlane/report.xml` (`:28`) are already covered upstream. |
**Drop `fastlane-plugin-versioning`.** The legacy carries it but never uses it — its `update_marketing_version`
is raw `File.read`/`gsub!`, and no `increment_version_number` / `get_version_number` / `update_info_plist`
call exists anywhere. Copying it into a file we call "slim and purpose-built" would propagate dead weight.
### `.env.default`
```
PROJECT_PATH=ElementX.xcodeproj
SCHEME=ElementX
MAIN_TARGET=ElementX
NSE_TARGET=NSE
SHARE_EXTENSION_TARGET=ShareExtension
MAIN_BUNDLE_ID=ch.healthchat.element
NSE_BUNDLE_ID=ch.healthchat.element.nse
SHARE_EXTENSION_BUNDLE_ID=ch.healthchat.element.shareextension
DERIVED_DATA_PATH=build
IPA_NAME=ElementX
ARCHIVE_PATH=out/ElementX.xcarchive
BUILD_OUTPUT_DIRECTORY=out
BUILD_LOG_DIRECTORY=build/Logs/Gym/
PROVISIONING_PROFILES_PATH=./provisioning_profiles/
MAIN_PROVISIONING_PROFILE_FILENAME=main.mobileprovision
NSE_PROVISIONING_PROFILE_FILENAME=nse.mobileprovision
SHARE_EXTENSION_PROVISIONING_PROFILE_FILENAME=share_extension.mobileprovision
APPSTORE_CODESIGNING_IDENTITY="Apple Distribution: E-HealthSolutions GmbH (6G37K72W82)"
APPSTORE_SIGNING_CERTIFICATE="Apple Distribution: E-HealthSolutions GmbH (6G37K72W82)"
APPSTORE_MAIN_PROVISIONING_PROFILE_SPECIFIER="HealthChat App Store"
APPSTORE_NSE_PROVISIONING_PROFILE_SPECIFIER="HealthChat NSE: App Store"
APPSTORE_SHARE_EXTENSION_PROVISIONING_PROFILE_SPECIFIER="HealthChat Share Extension: App Store"
TEAM_ID=6G37K72W82
# APPLE_ID is a machine-level shell variable on the runner, not a repo secret.
```
`APPSTORE_CODESIGNING_IDENTITY` and `APPSTORE_SIGNING_CERTIFICATE` are **distinct keys** in the legacy — both
are needed. The `out/ElementX.ipa` artifact path is only true because `BUILD_OUTPUT_DIRECTORY`/`IPA_NAME` are
set here.
Verified: bundle ids from `NSE/SupportingFiles/target.yml:75` and `ShareExtension/SupportingFiles/target.yml:77`;
schemes/targets from `ElementX/SupportingFiles/target.yml:3-4,41`, `NSE/SupportingFiles/target.yml:3-4,30`,
`ShareExtension/SupportingFiles/target.yml:3-4,30`.
### `release_testflight` lane
Mirrors `element-ios/fastlane/Fastfile:199-224`: accept inputs via options (local) or env vars (CI, avoiding
shell-escaping of multi-line notes), then:
1. `app_store_connect_api_key(...)` from `APPSTORECONNECT_KEY_{ID,ISSUER_ID,CONTENT}` when `is_ci`.
2. `generate_build_number` → `YYYYMMDDHHMMSS` (ported from `Fastfile:562`). Monotonic; makes retries free.
3. `xcodegen(spec: "project.yml")`.
4. `download_provisioning_profiles` → three profiles (main, NSE, ShareExtension).
5. `disable_automatic_code_signing` + `update_project_provisioning` per target.
6. `build_app`, `export_method: "app-store"`, `CODE_SIGN_STYLE=Manual`.
7. `upload_to_testflight(skip_submission: true, distribute_external: false, changelog: release_notes)`.
**Version injection.** element-x holds `MARKETING_VERSION: 26.07.3` / `CURRENT_PROJECT_VERSION: 1` in
`project.yml:56-57`, consumed by XcodeGen at *generate* time (unlike the legacy's `AppVersion.xcconfig`, read
at *build* time). Pass both via `xcargs` to `build_app` instead of rewriting `project.yml`. Verified sound:
NSE (`:51-52,76-77`) and ShareExtension (`:46-47,78-79`) both resolve their version keys from
`$(MARKETING_VERSION)`/`$(CURRENT_PROJECT_VERSION)` at build time, and xcodebuild command-line settings
override project *and* target level uniformly — so all three targets stay version-consistent. This matters:
App Store rejects an upload whose extension version disagrees with the app's.
This spares `project.yml`, but **it does not mean "no tracked file is mutated"** — `ElementX.xcodeproj/project.pbxproj`
is tracked and steps 3 and 5 rewrite it (xcodegen regenerates; `disable_automatic_code_signing` /
`update_project_provisioning` write Manual signing and profile UUIDs). The pbxproj footgun **moved, it did not
vanish**. The skill must say: never commit the post-lane tree; `git checkout -- ElementX.xcodeproj`.
### GitHub workflow
`.github/workflows/release-healthchat.yml` — near-copy of the legacy's, which is already well-built (pinned
action SHAs, `contents: read`, concurrency group, 90-minute timeout, awk notes extraction, artifact upload).
Changes from the legacy:
- **`submodules: false`** — critical. element-x-ios's only submodule is the **private**
`element-hq/element-ios-enterprise` over SSH; the legacy's `submodules: 'true'` would fail checkout on step
one of every run.
- Drop the CocoaPods cache and `rm -rf Pods/MatrixSDK`.
- Drop `MX_GIT_BRANCH` (legacy `:41`) — no `mx_git_branch` helper exists in the slim Fastfile.
- Re-evaluate `fetch-depth: 0` (legacy `:48`); keep only if something needs history.
- **Drop the `brew bundle` step** (legacy `:115`) — there is no `Brewfile`, and the runner is provisioned by
hand. See runner notes.
- `bundle exec fastlane release_testflight`; artifacts `out/ElementX.ipa`, `out/ElementX.app.dSYM.zip`.
- Harden the notes step with `[[ -f HEALTHCHAT_CHANGES.md ]]`. The legacy's "warning fallback" only fires on
an *empty match*, not an absent file — under `set -euo pipefail` a missing file aborts the job.
**Do not carry `HEALTHCHAT_KEYCHAIN_PASSWORD` forward as mandatory.** The legacy skill lists it among six
required secrets and the legacy workflow references it, but it exists at neither repo nor org level — and the
pipeline runs green regardless. `apple-actions/import-codesign-certs` tolerates its absence. **Five secrets is
the real requirement.**
### Runner
`novaloop-mac-studio-001` — org-level, online, labels `[self-hosted, macOS, ARM64]`; already satisfies
`runs-on: [self-hosted, macOS]`. Confirm the org runner group's visibility includes element-x-ios (two
`Default` groups exist, one `all`, one `selected`).
**No Xcode pin.** Measured on the runner today (user `novaloop`, arm64, macOS 26.5.2): exactly **one** Xcode,
`/Applications/Xcode.app` — **26.6, build `17F113`**, `xcode-select`ed and the only version `xcodes` lists. No
versioned `Xcode_*.app` siblings exist.
An earlier draft said to pin `"26.4"` because "the runner has it and the legacy pipeline proves it works".
**Both halves were wrong.** The runner does not have 26.4 and never will; a pin naming an absent version does
not fail fast — `xcodes` starts a multi-GB authenticated download needing `XCODES_USERNAME`/`XCODES_PASSWORD`,
which none of the five secrets supply, so `before_all` hangs and takes the release with it. Since the runner
carries a single globally-selected Xcode, whatever `xcode-select` points at *is* the build toolchain, and a pin
could only ever disagree with reality. **Drop it entirely**; the Xcode version becomes a property of the
machine, changed by provisioning it rather than by editing a tracked file. The reasoning about
`ci_scripts/ci_common.sh:34` (`sudo xcode-select -s /Applications/Xcode_26.5.0.app`) still stands — it is an
Xcode Cloud image path, not an `xcodes` version identifier, and says nothing about this runner.
The cost of dropping it: **whatever the runner has is what ships**, and no release run records which Xcode
built it (`release-healthchat.yml` does not log `xcodebuild -version`; `unit-tests.yml` does). Upgrading the
runner's Xcode is therefore a release-affecting change with no tag, diff or review. Accepted — the alternative
pins a version that does not exist. Captured in the skill.
**The runner is provisioned by hand, never by CI.** Upstream's `setup_github_actions_environment`
(`ci_scripts/ci_common.sh`) cannot run here: it calls `sudo xcode-select -s /Applications/Xcode_26.5.0.app` —
there is **no passwordless sudo** and no such path — and `brew update && brew install …`, which on this
*shared release machine* could upgrade `xcodegen` underneath a running release. CI must **verify and fail
loudly**, naming the fix, instead.
Present today: `xcodes`, `xcodegen`, `git-lfs`, `brew`, `swift`, rbenv `ruby`/`bundle`/`gem`, and `APPLE_ID`
exported as a machine-level shell variable (the `Appfile` reads it; it is not repo-carried).
**Missing today** — the human installs these once, before the test workflow can pass: `swiftlint`,
`swiftformat`, `pkl`, `xcresultparser`, `xcbeautify`. Only `xcresultparser` is outside homebrew-core
(`a7ex/homebrew-formulae/xcresultparser`). The unit-test job asserts all of them. The release lane requires
none: its SwiftLint/SwiftFormat build phases are `if which …` guarded and degrade to a build warning when the
tool is absent (`ElementX/SupportingFiles/target.yml:202-214`), and nothing in the build reads `pkl`.
### Secrets to copy
element-x-ios has **zero** repository secrets. Copy these five from `Novaloop-AG/element-ios`:
`HEALTHCHAT_ASC_KEY_ID`, `HEALTHCHAT_ASC_KEY_ISSUER_ID`, `HEALTHCHAT_ASC_KEY_CONTENT`,
`HEALTHCHAT_CERTIFICATES_P12`, `HEALTHCHAT_CERTIFICATES_P12_PASSWORD`.
Requires the `snowping` GitHub account — `Novaloop-AG/element-x-ios` is INTERNAL and invisible to
`joel-poyet_GALGH`.
---
## Part 3 — Release skill (PR3, `pr-doc`)
`.agents/skills/releasing-healthchat/SKILL.md`, reachable as a project skill through the existing
`.claude/skills -> ../.agents/skills` symlink.
Mirror the legacy skill's shape — it is a good model: two-field frontmatter (`name`, `description`), terse
imperative prose, checklists and tables, **rationale inline on every prohibition**. Sections: preflight,
cutting a release, `workflow_dispatch` retry path, tag format, release notes, post-release checks, required
secrets, runner requirements, certificate renewal, TestFlight → App Store promotion, rollback, divergence.
Corrected for this repo:
- **Five** required secrets, not six.
- Version source is `project.yml:56`, not `Config/AppVersion.xcconfig`.
- Three profiles (main, NSE, **ShareExtension**), not main/NSE/broadcast.
- **Never commit the post-lane tree** — `git checkout -- ElementX.xcodeproj` after a local run.
- **Test versions must be numeric-only** (see Verification) — this trap costs 30 minutes per hit.
- The divergence section shrinks: `fastlane/` and the release workflow are fork-only here, so the legacy's
`--ours` guidance for `.env.default`/`Fastfile` does not apply. What *does* need calling out:
`automatic-calendar-version.yml` and `post-release.yml` are gated on
`github.repository == 'element-hq/element-x-ios'` and silently no-op in the fork, so rebases drag
`project.yml`'s version along — **the tag is the source of truth**.
- **New section — the migration contract** (no legacy equivalent): `CLASSIC_APP_*` in `healthchat.yml` is
load-bearing for existing users and fails *silently*; it must never be "cleaned up" to upstream's values.
The `.requiresBackup` edit in `AuthenticationStartScreenViewModel` is a deliberate upstream divergence — a
rebase that reverts it reintroduces a broken CTA. Both apps share the `group.ch.healthchat` UserDefaults
suite (`AppSettings.swift:56`), so unqualified `@UserPreference` property names land in a suite the legacy
also writes to — future key collisions are cheap to introduce.
Add a 2-line delegation in `AGENTS.md` pointing at the skill rather than duplicating the runbook — the legacy
does exactly this (`element-ios/CLAUDE.md:288-290`). element-x's `AGENTS.md` has no release section today, and
per its own "keep current" rule this is a new fact about the repo, so it belongs there, in caveman voice.
---
## Landing plan
`Dangerfile.swift:50-51` **fails** the PR unless it carries exactly one `pr-` label. (The empty-body rule
`:24` and the >1000-additions rule `:18-19` are `warn`s, not fails.)
| PR | Contents | Label |
|---|---|---|
| PR1 | Part 1 — `CLASSIC_APP_*`, entitlement, `.requiresBackup` edit, regenerated pbxproj | `pr-bugfix` |
| PR2 | Part 2 — fastlane, workflow, Gemfile(.lock), .gitignore, HEALTHCHAT_CHANGES.md | `pr-build` |
| PR3 | Part 3 — skill + AGENTS.md delegation | `pr-doc` |
AGENTS.md's 500-addition guidance argues for keeping these separate rather than combining PR2 and PR3.
---
## Apple Developer portal prerequisites
Manual, one-time, **blocking** — no signed archive is possible until all three are done.
1. **Create App ID `ch.healthchat.element.shareextension`** with App Groups + Keychain Sharing, then the
`HealthChat Share Extension: App Store` profile. Genuinely new: the legacy provisioned
`ch.healthchat.element.shareExtension` — **camelCase, a different string** — and disabled the target anyway
(`element-ios/fastlane/.env.default`: `SHARE_EXTENSION_TARGET=`). element-x embeds ShareExtension
unconditionally (`ElementX/SupportingFiles/target.yml:227-228`).
2. **Add `com.apple.developer.usernotifications.communication`** to `ch.healthchat.element`; regenerate
`HealthChat App Store`. New vs. legacy (absent from `Riot/SupportingFiles/Riot.entitlements`).
3. **Add `com.apple.developer.usernotifications.filtering`** to `ch.healthchat.element.nse`; regenerate
`HealthChat NSE: App Store`. New vs. legacy (`RiotNSE.entitlements` has only app-groups + keychain).
The `HealthChat Broadcast Upload Extension: App Store` profile becomes orphaned — leave it; the legacy
pipeline still uses it.
Certificate: **not due.** `HEALTHCHAT_CERTIFICATES_P12` was updated 2026-04-07 and the workflow ran green
2026-04-15. Apple Distribution certs last one year → renewal due around **April 2027**.
---
## Verification
### Part 1 — device-only
**Step 0 (or the test silently false-negatives).** `AuthenticationService.swift:49-52` short-circuits
`loadAccounts()` entirely when `appSettings.hasSignedInBefore` — which lives in the `group.ch.healthchat`
UserDefaults suite (`AppSettings.swift:56`), survives app deletion via the shared container, and is never
cleared. **Any dev device that has previously run a HealthChat element-x build will show a plain login screen
while the fix works perfectly.** Delete the app before step 1. A `"skipping loadAccounts"` log line means the
run is **void, not failed**.
1. Install the *shipping* legacy HealthChat on a physical device. Sign in. Send an encrypted message so the
crypto store is populated.
2. Build element-x with the fix and install **over it** — same bundle id → in-place update. **Do not delete
the app first**; that destroys the keychain and container and invalidates the test.
3. Expected: the app finds the account and migrates without a re-login.
4. Confirm the legacy MXID is `@user:healthchat.ch` (the D2 precondition).
5. Exercise **both** secrets paths if possible: `.complete` (migrates) and `.requiresBackup` (must fall
through to sign-in, never the backup sheet).
6. Watch the log for `ClassicAppManagerError` — the failure is silent in the UI.
7. **Send a push notification to the migrated device.** Part 2 adds two brand-new notification entitlements
and a first-ever NSE profile; nothing else exercises delivery, and upload proves signing, not push.
### Part 2 — pipeline
Use `workflow_dispatch` with a throwaway version, as the legacy skill prescribes for cert renewal:
1. Run "Release HealthChat" manually with version **`26.7.99`** and a release-notes override.
**The version must be numeric-only.** It lands verbatim in `CFBundleShortVersionString` (`Info.plist:36-37`)
and Apple requires ≤3 period-separated integers. A marker like `26.07.3-pipeline-test` passes the
workflow's own regex (`:59`) but earns an **ITMS rejection at the final step, after ~30 minutes of
signing** — indistinguishable from a real signing failure. Put markers in the tag or release notes only.
2. Green run → build appears in App Store Connect → TestFlight within ~10 minutes.
3. Discard the build.
Do **not** verify by submitting to the App Store. TestFlight acceptance proves signing, entitlements and
upload; review proves nothing extra about the pipeline.
`swiftformat --lint .` and SwiftLint run on build and are currently clean.
---
## Risks & open questions
| # | Risk | Status |
|---|---|---|
| **O1** | **Was the legacy build on the Rust crypto store?** `cryptoSDKStoreKey` is generated unconditionally (`EncryptionKeyManager.swift:42`), but whether the store is Rust-backed decides if `SecretsBundleWithUserId.fromDatabase` can read it. If it was still Olm, D2 collapses to "forced re-login" and needs product sign-off. **Settle on a real device before the first submission.** |
| **O2** | **iOS floor rises 15.1 → 18.5** (`element-ios/Config/Project.xcconfig:29` vs `project.yml:14-15`). Every user below 18.5 silently stays on 1.4.1 forever — the App Store will not offer the update. Not fixable (upstream's floor); check the install base before shipping. |
| **O3** | **Pusher overwrite.** element-x's `pusherAppID` and device pushkey are identical to the legacy's, so first launch overwrites the user's sygnal pusher in place and the format changes to `.eventIdOnly`. Benign under D1 — but a rollback to 1.4.1 loses push. |
| **O4** | **Runner group visibility** — confirm `novaloop-mac-studio-001` is reachable from element-x-ios. |
| **O5** | `Secrets/Secrets.swift` holds placeholders; `Secrets/Secrets.pkl:12-17` wants MAPLIBRE_API_KEY among others. Maps degrade without it. Not a release blocker — track separately. |
| **O6** | Cosmetic Element strings ship in a HealthChat binary, e.g. `BGTaskSchedulerPermittedIdentifiers: io.element.elementx.background.refresh` (`ElementX/SupportingFiles/target.yml:102-104`). Working; cosmetic only. |
| **O7** | **The legacy element-ios pipeline is probably broken right now — unverified, and worth verifying.** It pins `xcodes(version: "26.4")` and runs on this same runner, which carries only **26.6**. Its last green run was 2026-04-15, so the machine plausibly had 26.4 then and lost it to an upgrade since. If so, the next legacy release does not fail — it **hangs** in `before_all` on an authenticated multi-GB download, and only a timeout ends it. **This fork no longer depends on that pipeline** (D3's "the legacy pipeline is proven" means *was* proven, on a toolchain the machine no longer has), so this is not a blocker here. Confirm the pin against `Novaloop-AG/element-ios` and de-pin it there before its next release. Out of scope for these three PRs — raise it. |
| **O8** | **Whatever Xcode the runner has is what ships.** The pin is gone (see Runner), so upgrading `/Applications/Xcode.app` silently changes the compiler for every subsequent release, with no tag, diff or review. `release-healthchat.yml` records it via the `Record toolchain` step (`xcodebuild -version`) before every build, so each run logs the compiler that produced it — but that log line is the only record, and it detects the change after the fact rather than preventing it. Treat a runner Xcode upgrade as a release-affecting change. |
| **O9** | **A pull request can execute arbitrary code on the release machine.** `unit-tests.yml` runs on `[self-hosted, macOS]` under a `pull_request` trigger (`.github/workflows/unit-tests.yml:9,20`), so any PR's build scripts run on `novaloop-mac-studio-001` — the same persistent runner, keychain and signing identities the release pipeline uses. **Accepted because the repo is INTERNAL**, which narrows PR authors to org members. The trade-off is invisible to CI: zizmor reports it (`self-hosted-runner`, medium) **only at `--persona=auditor`**, and the default persona CI runs suppresses it — so no required check will ever warn. If org membership stops being a sufficient trust boundary: restrict the trigger to non-fork branches, or move back to GitHub-hosted `macos-26` with a raised `timeout-minutes`. |
**Closed:** the app-group collision question — container paths are disjoint, and the shared
`group.ch.healthchat` UserDefaults suite has a verified-empty key intersection. Captured as a standing hazard
in the Part 3 migration contract rather than an open risk.
### Corrected prior belief
An earlier session recorded that a legacy app-lock PIN could lock the new build out via a shared keychain
group. **Refuted.** The two differ in *group, service and account* — `…keychain.shared` vs `…element`;
`…pin-service` vs `…keychain.sessions`; `pin` vs `appLockPINCode`
(`element-ios/Riot/Modules/SetPinCode/PinCodePreferences.swift:20-24,37-38` vs
`ElementX/.../KeychainController.swift:21-23,34-37`). The groups were never shared. The conclusion
("harmless") was right; the mechanism was wrong — and the wrong mechanism is what motivated dropping the
classic keychain group in `01dc703b4`.
## Out of scope
App Store submission automation (needs `fastlane/metadata/` + HealthChat screenshots, neither exists), German
localisation, the `to.healthchat.ch` links service, blue outgoing bubbles.
+2 -2
View File
@@ -53,7 +53,7 @@ settings:
ENABLE_BITCODE: false ENABLE_BITCODE: false
APP_NAME: ElementX APP_NAME: ElementX
KEYCHAIN_ACCESS_GROUP_IDENTIFIER: "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)" KEYCHAIN_ACCESS_GROUP_IDENTIFIER: "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)"
MARKETING_VERSION: 26.07.3 MARKETING_VERSION: 26.07.4
CURRENT_PROJECT_VERSION: 1 CURRENT_PROJECT_VERSION: 1
SUPPORTS_MACCATALYST: false SUPPORTS_MACCATALYST: false
@@ -68,7 +68,7 @@ include:
- path: UITests/SupportingFiles/target.yml - path: UITests/SupportingFiles/target.yml
- path: IntegrationTests/SupportingFiles/target.yml - path: IntegrationTests/SupportingFiles/target.yml
- path: AccessibilityTests/SupportingFiles/target.yml - path: AccessibilityTests/SupportingFiles/target.yml
- path: Variants/HealthChat/healthchat.yml # - path: MyAppVariant/override.yml
packages: packages:
# Element/Matrix dependencies # Element/Matrix dependencies