mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-09 07:20:07 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5743f263b6 |
@@ -11,11 +11,6 @@ permissions: {}
|
||||
jobs:
|
||||
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
|
||||
timeout-minutes: 150
|
||||
|
||||
|
||||
@@ -20,11 +20,6 @@ permissions: {}
|
||||
|
||||
jobs:
|
||||
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
|
||||
timeout-minutes: 15
|
||||
|
||||
|
||||
@@ -12,11 +12,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
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:
|
||||
# Only allow a single run of this workflow on each branch, automatically cancelling older runs.
|
||||
group: ${{ format('danger-ci-{0}', github.ref) }}
|
||||
|
||||
@@ -11,13 +11,6 @@ permissions: {}
|
||||
jobs:
|
||||
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
|
||||
timeout-minutes: 45
|
||||
|
||||
|
||||
@@ -15,12 +15,6 @@ permissions: {}
|
||||
jobs:
|
||||
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
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
|
||||
@@ -17,14 +17,7 @@ jobs:
|
||||
timeout-minutes: 45
|
||||
|
||||
# Skip in forks
|
||||
# HealthChat: this job can never pass in this fork — it needs
|
||||
# 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) }}
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
|
||||
|
||||
concurrency:
|
||||
# Only allow a single run of this workflow on each branch, automatically cancelling older runs.
|
||||
|
||||
@@ -13,78 +13,24 @@ permissions: {}
|
||||
jobs:
|
||||
tests:
|
||||
name: Tests
|
||||
# HealthChat: GitHub-hosted macos-26 could not finish this job inside
|
||||
# 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.
|
||||
runs-on: macos-26
|
||||
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:
|
||||
# Only allow a single run of this workflow on each branch, automatically cancelling older runs.
|
||||
group: ${{ format('unit-tests-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
|
||||
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
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# HealthChat: verify only — never provision. Upstream's
|
||||
# setup_github_actions_environment cannot run on this runner: it calls
|
||||
# `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: Setup environment
|
||||
run: source ci_scripts/ci_common.sh && setup_github_actions_environment
|
||||
|
||||
- name: Run tests
|
||||
# HealthChat: previews are skipped. PreviewTests renders a snapshot for every preview in the
|
||||
# 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
|
||||
run: swift run -q tools ci unit-tests
|
||||
|
||||
- name: Archive artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
@@ -98,11 +44,7 @@ jobs:
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
# Skip if not successful and in forks
|
||||
# HealthChat: the existing guard only asks whether the PR comes FROM a
|
||||
# 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) }}
|
||||
if: ${{ success() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
report_type: coverage
|
||||
@@ -114,9 +56,7 @@ jobs:
|
||||
|
||||
- name: Upload test results to Codecov
|
||||
# Skip if cancelled and in forks
|
||||
# HealthChat: gated on the upstream repo for the same reason as the
|
||||
# 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) }}
|
||||
if: ${{ !cancelled() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
continue-on-error: true
|
||||
with:
|
||||
|
||||
@@ -14,8 +14,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
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:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
+48
@@ -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)
|
||||
|
||||
### What's Changed
|
||||
|
||||
@@ -615,7 +615,6 @@
|
||||
659E5B766F76FDEC1BF393A4 /* RoomDetailsEditScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E413F4CBD7BF0588F394A9DD /* RoomDetailsEditScreenViewModel.swift */; };
|
||||
661EF50C1F7D4B0BC8A7AAE3 /* EmoteRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44ABA63DBE7F76C58260B43B /* EmoteRoomTimelineView.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 */; };
|
||||
66832DE7B5C2E861045265DC /* RoomSelectionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D879DC5515B1D42577F96C94 /* RoomSelectionScreen.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 */; };
|
||||
6CD61FAF03E8986523C2ABB8 /* StartChatScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3005886F00029F058DB62BE /* StartChatScreenCoordinator.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 */; };
|
||||
6E0C68918BEC5D27A4830BA6 /* PlaybackSpeedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBB8E5436E1BE0D6814BB7D /* PlaybackSpeedButton.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 */; };
|
||||
9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.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 */; };
|
||||
9603EEF6DE980BB1D15D4707 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A3E8741D199CD1A37F4CBF /* UIView.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 */; };
|
||||
E79D79CDAFE8BEBCC3AECA54 /* AppLockScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08283301736A6FE9D558B2CB /* AppLockScreenViewModelProtocol.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 */; };
|
||||
E82E13CC3EB923CCB8F8273C /* TimelineProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E543072DE58E751F028998 /* TimelineProxy.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -2943,8 +2938,6 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -2953,7 +2946,6 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -3596,7 +3588,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B6E4AB573FAEBB7B853DD04C /* AppHooks.swift */,
|
||||
801BA878EE879E01E82F3A5F /* HealthChat */,
|
||||
2ED530A4DD98266D31E5D8E1 /* Hooks */,
|
||||
);
|
||||
path = AppHooks;
|
||||
@@ -5375,17 +5366,6 @@
|
||||
path = View;
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -7500,34 +7480,34 @@
|
||||
ORGANIZATIONNAME = Element;
|
||||
TargetAttributes = {
|
||||
0E28CD62691FDBC63147D5E3 = {
|
||||
DevelopmentTeam = 6G37K72W82;
|
||||
DevelopmentTeam = 7J4U792NQT;
|
||||
TestTargetID = C0FAEB81CFD9776CD78CE489;
|
||||
};
|
||||
19F0C845D67E9BEA4BE7133E = {
|
||||
DevelopmentTeam = "$(DEVELOPMENT_TEAM)";
|
||||
};
|
||||
32C23C8D224D46EFE62AFAD0 = {
|
||||
DevelopmentTeam = 6G37K72W82;
|
||||
DevelopmentTeam = 7J4U792NQT;
|
||||
};
|
||||
676A87694F0F247B2AF1A142 = {
|
||||
DevelopmentTeam = 6G37K72W82;
|
||||
DevelopmentTeam = 7J4U792NQT;
|
||||
};
|
||||
7A17BE29BAC81ADBAC6349D9 = {
|
||||
DevelopmentTeam = 6G37K72W82;
|
||||
DevelopmentTeam = 7J4U792NQT;
|
||||
};
|
||||
C0C687DE1D270F9895FEE186 = {
|
||||
DevelopmentTeam = 6G37K72W82;
|
||||
DevelopmentTeam = 7J4U792NQT;
|
||||
TestTargetID = C0FAEB81CFD9776CD78CE489;
|
||||
};
|
||||
C0FAEB81CFD9776CD78CE489 = {
|
||||
DevelopmentTeam = "$(DEVELOPMENT_TEAM)";
|
||||
};
|
||||
D3DB351B7FBE0F49649171FC = {
|
||||
DevelopmentTeam = 6G37K72W82;
|
||||
DevelopmentTeam = 7J4U792NQT;
|
||||
TestTargetID = C0FAEB81CFD9776CD78CE489;
|
||||
};
|
||||
F8E276FD6DC43EADB85241BC = {
|
||||
DevelopmentTeam = 6G37K72W82;
|
||||
DevelopmentTeam = 7J4U792NQT;
|
||||
};
|
||||
FEB53A5BC378C913769656D8 = {
|
||||
DevelopmentTeam = "$(DEVELOPMENT_TEAM)";
|
||||
@@ -8530,10 +8510,6 @@
|
||||
4295E5F850897710A51AE114 /* GeoURI.swift in Sources */,
|
||||
F3C9CAD26FD4D7D6EBACF501 /* HTMLFixtures.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 */,
|
||||
964B9D2EC38C488C360CE0C9 /* HomeScreen.swift in Sources */,
|
||||
62C5876C4254C58C2086F0DE /* HomeScreenContent.swift in Sources */,
|
||||
@@ -9857,11 +9833,11 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
APP_DISPLAY_NAME = HealthChat;
|
||||
APP_GROUP_IDENTIFIER = group.ch.healthchat;
|
||||
APP_DISPLAY_NAME = "Element X";
|
||||
APP_GROUP_IDENTIFIER = group.io.element;
|
||||
APP_NAME = ElementX;
|
||||
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_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
@@ -9899,7 +9875,7 @@
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 6G37K72W82;
|
||||
DEVELOPMENT_TEAM = 7J4U792NQT;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -9915,10 +9891,10 @@
|
||||
IS_NIGHTLY_BUILD = NO;
|
||||
KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)";
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.5;
|
||||
MARKETING_VERSION = 26.07.3;
|
||||
MARKETING_VERSION = 26.07.4;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCTION_APP_NAME = HealthChat;
|
||||
PRODUCTION_APP_NAME = Element;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
@@ -9932,11 +9908,11 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
APP_DISPLAY_NAME = HealthChat;
|
||||
APP_GROUP_IDENTIFIER = group.ch.healthchat;
|
||||
APP_DISPLAY_NAME = "Element X";
|
||||
APP_GROUP_IDENTIFIER = group.io.element;
|
||||
APP_NAME = ElementX;
|
||||
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_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
@@ -9974,7 +9950,7 @@
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 6G37K72W82;
|
||||
DEVELOPMENT_TEAM = 7J4U792NQT;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
@@ -9996,11 +9972,11 @@
|
||||
IS_NIGHTLY_BUILD = NO;
|
||||
KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)";
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.5;
|
||||
MARKETING_VERSION = 26.07.3;
|
||||
MARKETING_VERSION = 26.07.4;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCTION_APP_NAME = HealthChat;
|
||||
PRODUCTION_APP_NAME = Element;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 265 KiB After Width: | Height: | Size: 244 KiB |
@@ -1,23 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "app-logo.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "app-logo@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "app-logo@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"filename" : "app-logo.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
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_onboarding_welcome_title_ios" = "Welcome to HealthChat";
|
||||
"screen_search_empty_state_message" = "Search for chats and messages";
|
||||
"screen_search_empty_state_title" = "Start searching...";
|
||||
"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 {
|
||||
/// 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
|
||||
internal static var screenSearchEmptyStateMessage: String { return UntranslatedL10n.tr("Untranslated", "screen_search_empty_state_message") }
|
||||
/// Start searching...
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ struct AuthenticationStartScreen: View {
|
||||
|
||||
if !context.viewState.hideBrandChrome {
|
||||
VStack(spacing: 8) {
|
||||
Text(UntranslatedL10n.screenOnboardingWelcomeTitleIos)
|
||||
Text(L10n.screenOnboardingWelcomeTitle)
|
||||
.font(.compound.headingLGBold)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
@@ -6,9 +6,13 @@
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.associated-domains</key>
|
||||
<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>webcredentials:*.healthchat.ch</string>
|
||||
<string>webcredentials:*.element.io</string>
|
||||
</array>
|
||||
<key>com.apple.developer.usernotifications.communication</key>
|
||||
<true/>
|
||||
@@ -17,12 +21,14 @@
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>$(APP_GROUP_IDENTIFIER)</string>
|
||||
<string>$(CLASSIC_APP_GROUP_IDENTIFIER)</string>
|
||||
</array>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(KEYCHAIN_ACCESS_GROUP_IDENTIFIER)</string>
|
||||
<string>$(CLASSIC_APP_KEYCHAIN_ACCESS_GROUP_IDENTIFIER)</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(APP_DISPLAY_NAME)</string>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
|
||||
@@ -48,9 +48,6 @@ targets:
|
||||
UILaunchScreen:
|
||||
UIColorName: colors/background-color
|
||||
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)
|
||||
CFBundleVersion: $(CURRENT_PROJECT_VERSION)
|
||||
CFBundleURLTypes: [
|
||||
@@ -118,19 +115,23 @@ targets:
|
||||
properties:
|
||||
aps-environment: development
|
||||
com.apple.developer.associated-domains:
|
||||
# HealthChat: app.healthchat.ch universal links + webcredentials (OAuth/autofill). matrix.to kept for permalinks.
|
||||
- applinks:app.healthchat.ch
|
||||
- applinks:element.io
|
||||
- applinks:app.element.io
|
||||
- applinks:staging.element.io
|
||||
- applinks:develop.element.io
|
||||
- applinks:mobile.element.io
|
||||
- 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.security.app-sandbox: true
|
||||
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)
|
||||
- $(CLASSIC_APP_GROUP_IDENTIFIER)
|
||||
com.apple.security.network.client: true
|
||||
keychain-access-groups:
|
||||
# HealthChat: dropped $(CLASSIC_APP_KEYCHAIN_ACCESS_GROUP_IDENTIFIER) (im.vector.app.*) — not provisionable here.
|
||||
- $(KEYCHAIN_ACCESS_GROUP_IDENTIFIER)
|
||||
- $(CLASSIC_APP_KEYCHAIN_ACCESS_GROUP_IDENTIFIER)
|
||||
|
||||
settings:
|
||||
base:
|
||||
|
||||
@@ -55,16 +55,6 @@ struct RunTests: AsyncParsableCommand {
|
||||
"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 {
|
||||
if let createName = createSimulatorName {
|
||||
guard let createType = createSimulatorType else {
|
||||
@@ -152,10 +142,6 @@ struct RunTests: AsyncParsableCommand {
|
||||
command += " -only-testing:\(scheme)/\(testName)"
|
||||
}
|
||||
|
||||
for skippedTest in skippedTests {
|
||||
command += " -skip-testing:\(scheme)/\(skippedTest)"
|
||||
}
|
||||
|
||||
command += " | \(formatter)"
|
||||
|
||||
try await CI.run(.path("/bin/zsh"), ["-cu", command])
|
||||
|
||||
@@ -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
@@ -53,7 +53,7 @@ settings:
|
||||
ENABLE_BITCODE: false
|
||||
APP_NAME: ElementX
|
||||
KEYCHAIN_ACCESS_GROUP_IDENTIFIER: "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)"
|
||||
MARKETING_VERSION: 26.07.3
|
||||
MARKETING_VERSION: 26.07.4
|
||||
CURRENT_PROJECT_VERSION: 1
|
||||
SUPPORTS_MACCATALYST: false
|
||||
|
||||
@@ -68,7 +68,7 @@ include:
|
||||
- path: UITests/SupportingFiles/target.yml
|
||||
- path: IntegrationTests/SupportingFiles/target.yml
|
||||
- path: AccessibilityTests/SupportingFiles/target.yml
|
||||
- path: Variants/HealthChat/healthchat.yml
|
||||
# - path: MyAppVariant/override.yml
|
||||
|
||||
packages:
|
||||
# Element/Matrix dependencies
|
||||
|
||||
Reference in New Issue
Block a user