From 8277cfb88c445bbf1c775c0dac41321a1ca8410c Mon Sep 17 00:00:00 2001 From: Sedat Qaja Date: Mon, 19 Oct 2026 09:55:44 +0200 Subject: [PATCH] docs(healthchat): add the upstream sync skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch-stack model, LFS handling, conflict hot spots, verification matrix, force-push and rewind, collected from the 26.09.1 → 26.10.3 sync. --- .../syncing-upstream-healthchat/SKILL.md | 129 ++++++++++++++++++ AGENTS.md | 3 +- 2 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 .agents/skills/syncing-upstream-healthchat/SKILL.md diff --git a/.agents/skills/syncing-upstream-healthchat/SKILL.md b/.agents/skills/syncing-upstream-healthchat/SKILL.md new file mode 100644 index 000000000..9628a527b --- /dev/null +++ b/.agents/skills/syncing-upstream-healthchat/SKILL.md @@ -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/ tag release/ --no-tags`. Release tags are + `release/`. 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/..release/healthchat --no-merges # the fork's commits +git merge-base --is-ancestor release/ 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/ release/ 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/`, 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/`, rebase the original commits with conflicts resolved to + the fork side blindly (`git checkout --theirs`), then append fix commits whose content is + `git checkout -- ` per topic: + `chore(healthchat): resolve the rebase onto upstream ` (leftovers: workflows, package + pins, Enterprise pointer, start screen), one commit per rework topic, `test(healthchat): + refresh snapshots after rebase onto `, docs. Order of fix commits: resolution first. +3. `git diff --stat 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/` gets the tip only. Fetch the tree of every upstream + commit that touched LFS paths, one by one: + ```bash + git log --format=%H release/..release/ -- '*.png' '*.jpg' '*.gif' '*.mp4' '*.m4a' \ + | sort -u | while read c; do git lfs fetch upstream "$c"; done + git lfs push origin # 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 ` 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 :refs/heads/backup/release-healthchat-before- +git push --force-with-lease=release/healthchat: origin :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^{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). diff --git a/AGENTS.md b/AGENTS.md index 17923ef3e..0b069f585 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. ---