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:
Sedat Qaja
2026-10-19 09:55:44 +02:00
parent 6f931434b5
commit 8277cfb88c
2 changed files with 131 additions and 1 deletions
@@ -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).
+2 -1
View File
@@ -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.
---