mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 08:15:24 +00:00
docs(healthchat): add the upstream sync skill
Patch-stack model, LFS handling, conflict hot spots, verification matrix, force-push and rewind, collected from the 26.09.1 → 26.10.3 sync.
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
---
|
||||
name: syncing-upstream-healthchat
|
||||
description: Bring the HealthChat fork (release/healthchat) onto a newer element-hq/element-x-ios release tag. Use when the user asks to rebase, sync, merge or update to upstream, take a new Element X version, or asks why the fork is behind upstream. Covers the patch-stack model, LFS, conflicts, verification, the force-push and the rewind.
|
||||
---
|
||||
|
||||
# Syncing the HealthChat fork with upstream
|
||||
|
||||
`release/healthchat` = upstream release tag + HealthChat commits on top. Patch stack, same as
|
||||
healthchat-web. A sync replaces the stack's base and force-pushes the branch. That is the one
|
||||
sanctioned history rewrite in this repo; everything else in `AGENTS.md` still holds.
|
||||
|
||||
First done 26.09.1 → 26.10.3 (2026-10-16 to 10-19). Numbers below come from that run.
|
||||
|
||||
## Before touching anything
|
||||
|
||||
- [ ] **Toolchain.** Upstream pins Xcode + iOS in its CI (`runs-on:` in `.github/workflows/`,
|
||||
`#available(iOS N)` in sources). Dev Mac AND the runner need it. 26.10.3 needed Xcode 27 /
|
||||
iOS 27 SDK; the runner still had 26.6 and nobody knew — check `xcodebuild -version` on the
|
||||
Studio (`novaloops-mac-studio` on Tailscale, runner `novaloop-mac-studio-001`). Upgrade via
|
||||
App Store in place, open once for the licence, `xcodebuild -downloadPlatform iOS`.
|
||||
- [ ] **Upstream remote.** `git remote add upstream https://github.com/element-hq/element-x-ios.git`
|
||||
then `git fetch upstream tag release/<old> tag release/<new> --no-tags`. Release tags are
|
||||
`release/<version>`. After adding the remote, `gh` resolves to upstream — always pass
|
||||
`-R Novaloop-AG/element-x-ios`.
|
||||
- [ ] **A 1.4.1 for the migration test.** Legacy users still exist. No 1.4.1 exists anywhere
|
||||
(TestFlight builds expire, legacy pipeline last built it 2026-04). Build it from Xcode:
|
||||
worktree of `Novaloop-AG/element-ios` at tag `healthchat-v1.4.1`, submodule
|
||||
`matrix-ios-sdk`, `xcodegen`, `pod install`, open `Riot.xcworkspace`, scheme `Riot`,
|
||||
run on the device with automatic signing. Builds on Xcode 27 (warnings only).
|
||||
- [ ] **Decisions, product owner:** store version label (stays 2.x, bundle version stays
|
||||
calendar — see releasing skill), new upstream features on/off (26.10.3: search tab on,
|
||||
search breadcrumbs kept).
|
||||
|
||||
## The stack
|
||||
|
||||
```bash
|
||||
git log --oneline release/<old>..release/healthchat --no-merges # the fork's commits
|
||||
git merge-base --is-ancestor release/<old> release/healthchat # must be true
|
||||
```
|
||||
|
||||
Upstream files the fork edits — conflicts live here every time:
|
||||
`AuthenticationStartScreenViewModel` (migration fall-through, see releasing skill "The
|
||||
migration contract"), `AppHooks/UserHooks.swift` (two HealthChat hook fields),
|
||||
`AppSettings.swift` (VoIP push properties for the NSE), `ElementCallService.swift` (PushKit
|
||||
token, expiration parsing), `NSE/Sources/NotificationHandler.swift` (VoIP push to Sygnal),
|
||||
`UserSessionFlowCoordinator` (Members tab), `SettingsScreen*` (account status section),
|
||||
`RoomMemberDetailsScreen*` + `UserProfileScreen*` (extended profile section), the five fork-gated
|
||||
CI workflows, `.gitignore`, `project.pbxproj` (never resolve by hand — `xcodegen`).
|
||||
|
||||
## Two ways to rebuild the stack
|
||||
|
||||
**A. Plain rebase** — `git rebase --onto release/<new> release/<old> release/healthchat` on a
|
||||
scratch branch, resolve per commit. Faithful, but every conflicting commit must be resolved in
|
||||
its own context; upstream API removals (26.10.3: `AppHooks.configure(with:)`, `HomeTabsHook`,
|
||||
`CommonSettingsProtocol`, `appSettings` out of flow parameters) cannot be "resolved", they
|
||||
need new code. Expect rebased intermediates that do not compile; only the tip has to.
|
||||
|
||||
**B. Resolve once, assemble the stack** (used for 26.10.3):
|
||||
|
||||
1. Scratch branch off `release/healthchat`, `git merge --no-commit release/<new>`, resolve all
|
||||
conflicts once, add the rework commits until it builds and tests pass. This is the
|
||||
*final tree*. Verify it fully (next section) — the stack only reproduces it.
|
||||
2. Scratch branch off `release/<new>`, rebase the original commits with conflicts resolved to
|
||||
the fork side blindly (`git checkout --theirs`), then append fix commits whose content is
|
||||
`git checkout <final tree> -- <files>` per topic:
|
||||
`chore(healthchat): resolve the rebase onto upstream <new>` (leftovers: workflows, package
|
||||
pins, Enterprise pointer, start screen), one commit per rework topic, `test(healthchat):
|
||||
refresh snapshots after rebase onto <new>`, docs. Order of fix commits: resolution first.
|
||||
3. `git diff --stat <final tree> HEAD` must be empty.
|
||||
|
||||
Reproducing historic commits: commit with `--no-verify`, else the SwiftFormat hook rewrites
|
||||
them. `git cherry-pick` has no `-q`. In zsh, `for c in $VAR` does not split on newlines and a
|
||||
variable named `path` *is* `PATH` — use `while read`, never name a variable `path`.
|
||||
|
||||
## Git LFS — the part that bites
|
||||
|
||||
- Checkouts/merges done with `GIT_LFS_SKIP_SMUDGE=1` leave pointer files. Run
|
||||
`git lfs checkout` before any test run: media fixtures (`UnitTests/Resources`) as pointers
|
||||
fail `MediaUploadingPreprocessorTests`, `MediaProviderTests`, `VoiceMessageRecorderTests`.
|
||||
- origin is a standalone repo; upstream's LFS objects are not there. GitHub rejects a push
|
||||
whose *history* references LFS objects it lacks (`GH008 … unknown Git LFS objects`).
|
||||
`git lfs fetch upstream release/<new>` gets the tip only. Fetch the tree of every upstream
|
||||
commit that touched LFS paths, one by one:
|
||||
```bash
|
||||
git log --format=%H release/<old>..release/<new> -- '*.png' '*.jpg' '*.gif' '*.mp4' '*.m4a' \
|
||||
| sort -u | while read c; do git lfs fetch upstream "$c"; done
|
||||
git lfs push origin <branch> # must report no "(missing)"; then git push
|
||||
```
|
||||
A single missing object = one failed fetch; refetch that commit. 26.10.3: 35 commits, ~5 GB.
|
||||
- Snapshot recording (`swift run tools ci preview-tests --record`, iPhone SE 3rd gen,
|
||||
iOS version from `Tools/Sources/Commands/CI/CI.swift`) re-records upstream references too
|
||||
(284 of ~2400 differed on our Mac). Keep only `healthChat*` snapshots; revert upstream's.
|
||||
|
||||
## Verification of the final tree
|
||||
|
||||
- Build ElementX, NSE, ShareExtension (`xcodebuild build -scheme ElementX` covers all three).
|
||||
- Unit tests on an **iOS 27+ simulator** matching upstream's CI OS; two video-size tests are
|
||||
encoder-dependent and fail on older simulators for no reason.
|
||||
- Migration guard: `healthChatProviderAllowsLegacyAccount` in
|
||||
`AuthenticationStartScreenViewModelTests` (real hook values → `.welcomeBack`) and
|
||||
`classicAppAccountRequiresBackupFallsThroughToLogin` must pass.
|
||||
- Runner: push the branch, `gh workflow run unit-tests.yml --ref <branch>` if the
|
||||
`pull_request` trigger stays silent. A job whose predecessor was cancelled mid-run can crash
|
||||
the test host at launch ("Early unexpected exit … signal kill") — plain re-run.
|
||||
- Device matrix (owner): 1.4.1 → new on a never-used device (log "Already signed in to
|
||||
Element X before" = void run, "Classic App not configured" = broken), previous release → new,
|
||||
VoIP ringing on a Debug build (Sygnal `.dev` entries need `platform: sandbox`), push +
|
||||
badges, Members tab next to any new upstream tab.
|
||||
|
||||
## Force-push, rewind
|
||||
|
||||
```bash
|
||||
git push origin <old tip>:refs/heads/backup/release-healthchat-before-<new>
|
||||
git push --force-with-lease=release/healthchat:<old tip> origin <stack>:release/healthchat
|
||||
git checkout release/healthchat && git reset --hard origin/release/healthchat # every clone
|
||||
```
|
||||
|
||||
Branch is unprotected: the push lands immediately. Old release tags keep their commits alive
|
||||
and off the branch line. Rewind = `git push --force origin healthchat-v<last>^{commit}:release/healthchat`.
|
||||
A rewind does not remove a tag or TestFlight build made on the new history — tag only after
|
||||
the device tests. Then update `HEALTHCHAT_CHANGES.md`, the releasing skill (Xcode, divergence
|
||||
list) and this skill with what changed.
|
||||
|
||||
## What not to do
|
||||
|
||||
- Squash the fork into a side branch and *merge* upstream there: merging back into the
|
||||
unsquashed branch conflicts on every file (81 on 26.10.3) and duplicates every commit.
|
||||
- Resolve `project.pbxproj` by hand. Commit the post-lane project file. Pull on a rewritten
|
||||
branch (merges the old history back in).
|
||||
@@ -73,7 +73,8 @@ PRs must follow rules. Prefer Xcode MCP tools over terminal commands.
|
||||
HealthChat ship via tag `healthchat-v*` → TestFlight. Full runbook in
|
||||
`.agents/skills/releasing-healthchat/SKILL.md`. `release/healthchat` = upstream tag +
|
||||
HealthChat commits on top (patch stack). Upstream sync rebase stack, force-push branch —
|
||||
only rewrite allowed; see runbook. Read skill before release, cert
|
||||
only rewrite allowed; procedure in
|
||||
`.agents/skills/syncing-upstream-healthchat/SKILL.md`. Read skill before release, cert
|
||||
renewal, or touching `fastlane/`. No duplicate runbook here.
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user