When a pull request is opened (or updated) on chat_web_app, the self-hosted act runner must open a matching pull request on chat_android.
The Android PR is a tracking draft, not an automatic React → Kotlin port. It carries the web PR's intent, link, and changed paths so native work can land on its own branch. chat_android is Kotlin + Compose; llm-fe is the product source of truth (#111, chat_android#1).
Today nothing cross-posts. unit-tests.yml already runs on pull_request → master on runs-on: self-hosted. chat_android CI is separate and does not watch this repo.
Behavior
Trigger
New workflow .gitea/workflows/mirror-android-pr.yml in chat_web_app.
Push branch mirror/web-<number> with one commit that adds docs/web-mirrors/<number>.md (Gitea will not open a PR with an empty diff). Commit message: Track chat_web_app#<number>.
Open a draft pull request on chat_android:
Head:mirror/web-<number>
Base:chat_android default branch
Title:[web#<number>] <web PR title>
Body: see template below
Update
On synchronize (and on opened if the mirror branch already exists):
Find the existing Android PR by branch name mirror/web-<number> (fallback: search open PR bodies for the marker <!-- web-pr:<number> -->).
Refresh docs/web-mirrors/<number>.md and the PR body (new SHA, new file list, new web title/body).
Do not open a second PR.
Do not rewrite commits that a human added on top of the tracking commit. Only update the mirror doc (new commit on the same branch) and the PR description.
Close
On source PR closed:
Merged: comment on the Android PR with the merge SHA and web PR URL. Leave the Android PR open — native work may still be in progress.
Closed without merge: close the Android draft only if the branch contains no commits beyond the mirror-doc commits. If a person has pushed implementation commits, comment and leave it open.
Opt out
Skip create/update when the web PR body contains a line android: skip (case-insensitive). Still run the close path so an existing mirror is closed or commented. No repo labels exist today; do not depend on labels.
Loop guard
This workflow runs only in chat_web_app. Do not add a reverse mirror from chat_android back here.
Android PR body template
<!-- web-pr:<number> -->
Draft mirror of a web pull request. Implement the native equivalent on this branch. Do not expect a mechanical port — `chat_android` is Kotlin/Compose; web is `llm-fe`.
- **Web PR:** <html_url>
- **Author:** <user>
- **Head SHA:** <sha>
- **State:** <open|merged|closed>
## Web description
<web body>
## Files changed on web
<bullet list of paths>
## Native surfaces to check
Map touched `llm-fe` routes/screens to `chat_android` using `docs/SCREEN_INVENTORY.md` (Sign in, Sign up, Password reset, Dashboard/chat, Account, Documents, Analytics, Feedback). Write "none obvious" when the diff is docs, CI, or Capacitor-only (`llm-fe/android/`).
## Done when
- [ ] Native behavior matches the web PR's user-visible change, or this draft is closed with a comment explaining why Android is unaffected
The same content goes in docs/web-mirrors/<number>.md so the branch has a real diff and the link survives even if the PR body is edited.
Auth
github.token / the default Actions token is scoped to chat_web_app and cannot push or open PRs on chat_android.
Add a Gitea Actions secret on chat_web_app:
Secret
Purpose
CHAT_ANDROID_PR_TOKEN
Token that can push branches and open/update/close pull requests on ai_ml_operations/chat_android
Use that token for the clone, push, and API calls (https://git.aimloperations.com/api/v1/repos/ai_ml_operations/chat_android/...). Do not reuse the release token described in #121 unless it already has write on chat_android — prefer this dedicated secret.
Token must not be printed in logs.
Out of scope
Generating Kotlin from the web diff (no agent, no code translation in this workflow)
## Summary
When a pull request is opened (or updated) on `chat_web_app`, the self-hosted **act runner** must open a matching pull request on [`chat_android`](https://git.aimloperations.com/ai_ml_operations/chat_android).
The Android PR is a **tracking draft**, not an automatic React → Kotlin port. It carries the web PR's intent, link, and changed paths so native work can land on its own branch. `chat_android` is Kotlin + Compose; `llm-fe` is the product source of truth ([#111](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/111), [chat_android#1](https://git.aimloperations.com/ai_ml_operations/chat_android/issues/1)).
Today nothing cross-posts. `unit-tests.yml` already runs on `pull_request` → `master` on `runs-on: self-hosted`. `chat_android` CI is separate and does not watch this repo.
---
## Behavior
### Trigger
New workflow `.gitea/workflows/mirror-android-pr.yml` in `chat_web_app`.
- **On:** `pull_request` types `opened`, `synchronize`, `reopened`, `closed`
- **Branches:** `master`
- **Runner:** `self-hosted` (same act runner as Unit Tests / Deploy Beta / Deploy Prod)
### Create
On `opened` / `reopened`, if no mirror exists yet:
1. Read the source PR from the Actions event (`title`, `body`, `number`, `html_url`, `user`, head SHA, base branch, action).
2. List changed files (`git diff --name-only` against the PR base, or the Gitea pulls/files API).
3. Clone `ai_ml_operations/chat_android` default branch (`master`).
4. Push branch `mirror/web-<number>` with **one commit** that adds `docs/web-mirrors/<number>.md` (Gitea will not open a PR with an empty diff). Commit message: `Track chat_web_app#<number>`.
5. Open a **draft** pull request on `chat_android`:
- **Head:** `mirror/web-<number>`
- **Base:** `chat_android` default branch
- **Title:** `[web#<number>] <web PR title>`
- **Body:** see template below
### Update
On `synchronize` (and on `opened` if the mirror branch already exists):
- Find the existing Android PR by branch name `mirror/web-<number>` (fallback: search open PR bodies for the marker `<!-- web-pr:<number> -->`).
- Refresh `docs/web-mirrors/<number>.md` and the PR body (new SHA, new file list, new web title/body).
- Do **not** open a second PR.
- Do **not** rewrite commits that a human added on top of the tracking commit. Only update the mirror doc (new commit on the same branch) and the PR description.
### Close
On source PR `closed`:
- **Merged:** comment on the Android PR with the merge SHA and web PR URL. Leave the Android PR **open** — native work may still be in progress.
- **Closed without merge:** close the Android draft **only if** the branch contains no commits beyond the mirror-doc commits. If a person has pushed implementation commits, comment and leave it open.
### Opt out
Skip create/update when the web PR body contains a line `android: skip` (case-insensitive). Still run the close path so an existing mirror is closed or commented. No repo labels exist today; do not depend on labels.
### Loop guard
This workflow runs only in `chat_web_app`. Do not add a reverse mirror from `chat_android` back here.
---
## Android PR body template
```markdown
<!-- web-pr:<number> -->
Draft mirror of a web pull request. Implement the native equivalent on this branch. Do not expect a mechanical port — `chat_android` is Kotlin/Compose; web is `llm-fe`.
- **Web PR:** <html_url>
- **Author:** <user>
- **Head SHA:** <sha>
- **State:** <open|merged|closed>
## Web description
<web body>
## Files changed on web
<bullet list of paths>
## Native surfaces to check
Map touched `llm-fe` routes/screens to `chat_android` using `docs/SCREEN_INVENTORY.md` (Sign in, Sign up, Password reset, Dashboard/chat, Account, Documents, Analytics, Feedback). Write "none obvious" when the diff is docs, CI, or Capacitor-only (`llm-fe/android/`).
## Done when
- [ ] Native behavior matches the web PR's user-visible change, or this draft is closed with a comment explaining why Android is unaffected
```
The same content goes in `docs/web-mirrors/<number>.md` so the branch has a real diff and the link survives even if the PR body is edited.
---
## Auth
`github.token` / the default Actions token is scoped to `chat_web_app` and **cannot** push or open PRs on `chat_android`.
Add a Gitea Actions secret on `chat_web_app`:
| Secret | Purpose |
|--------|---------|
| `CHAT_ANDROID_PR_TOKEN` | Token that can push branches and open/update/close pull requests on `ai_ml_operations/chat_android` |
Use that token for the clone, push, and API calls (`https://git.aimloperations.com/api/v1/repos/ai_ml_operations/chat_android/...`). Do not reuse the release token described in [#121](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/121) unless it already has write on `chat_android` — prefer this dedicated secret.
Token must not be printed in logs.
---
## Out of scope
- Generating Kotlin from the web diff (no agent, no code translation in this workflow)
- Mirroring `chat_ios`
- Auto-merge of the Android PR
- Push-to-`master` events that are not pull requests
- Changing Unit Tests, Deploy Beta, or Deploy Prod
- Capacitor `llm-fe/android/` builds ([#57](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/57), [#121](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/121))
---
## Acceptance criteria
- [ ] Opening a PR against `chat_web_app` `master` makes the act runner open one **draft** PR on `chat_android` titled `[web#<number>] …` from branch `mirror/web-<number>`
- [ ] Android PR body contains the web link, description, changed paths, and the `<!-- web-pr:<number> -->` marker
- [ ] A second push to the same web PR updates that Android PR and does not create another one
- [ ] Web PR body line `android: skip` prevents a new mirror
- [ ] Merging the web PR comments on the Android PR and leaves it open
- [ ] Closing the web PR without merging closes the Android draft only when nobody has added commits beyond the mirror doc
- [ ] Workflow uses `CHAT_ANDROID_PR_TOKEN` and runs on `self-hosted`
- [ ] README CI table (or `ANDROID.md`) mentions the mirror workflow in one short paragraph
## References
- Runner pattern: `.gitea/workflows/unit-tests.yml` (`pull_request`, `self-hosted`)
- Native epic: [#111](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/111)
- Android app epic: [chat_android#1](https://git.aimloperations.com/ai_ml_operations/chat_android/issues/1)
- Screen map: `chat_android/docs/SCREEN_INVENTORY.md`
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
When a pull request is opened (or updated) on
chat_web_app, the self-hosted act runner must open a matching pull request onchat_android.The Android PR is a tracking draft, not an automatic React → Kotlin port. It carries the web PR's intent, link, and changed paths so native work can land on its own branch.
chat_androidis Kotlin + Compose;llm-feis the product source of truth (#111, chat_android#1).Today nothing cross-posts.
unit-tests.ymlalready runs onpull_request→masteronruns-on: self-hosted.chat_androidCI is separate and does not watch this repo.Behavior
Trigger
New workflow
.gitea/workflows/mirror-android-pr.ymlinchat_web_app.pull_requesttypesopened,synchronize,reopened,closedmasterself-hosted(same act runner as Unit Tests / Deploy Beta / Deploy Prod)Create
On
opened/reopened, if no mirror exists yet:title,body,number,html_url,user, head SHA, base branch, action).git diff --name-onlyagainst the PR base, or the Gitea pulls/files API).ai_ml_operations/chat_androiddefault branch (master).mirror/web-<number>with one commit that addsdocs/web-mirrors/<number>.md(Gitea will not open a PR with an empty diff). Commit message:Track chat_web_app#<number>.chat_android:mirror/web-<number>chat_androiddefault branch[web#<number>] <web PR title>Update
On
synchronize(and onopenedif the mirror branch already exists):mirror/web-<number>(fallback: search open PR bodies for the marker<!-- web-pr:<number> -->).docs/web-mirrors/<number>.mdand the PR body (new SHA, new file list, new web title/body).Close
On source PR
closed:Opt out
Skip create/update when the web PR body contains a line
android: skip(case-insensitive). Still run the close path so an existing mirror is closed or commented. No repo labels exist today; do not depend on labels.Loop guard
This workflow runs only in
chat_web_app. Do not add a reverse mirror fromchat_androidback here.Android PR body template
The same content goes in
docs/web-mirrors/<number>.mdso the branch has a real diff and the link survives even if the PR body is edited.Auth
github.token/ the default Actions token is scoped tochat_web_appand cannot push or open PRs onchat_android.Add a Gitea Actions secret on
chat_web_app:CHAT_ANDROID_PR_TOKENai_ml_operations/chat_androidUse that token for the clone, push, and API calls (
https://git.aimloperations.com/api/v1/repos/ai_ml_operations/chat_android/...). Do not reuse the release token described in #121 unless it already has write onchat_android— prefer this dedicated secret.Token must not be printed in logs.
Out of scope
chat_iosmasterevents that are not pull requestsllm-fe/android/builds (#57, #121)Acceptance criteria
chat_web_appmastermakes the act runner open one draft PR onchat_androidtitled[web#<number>] …from branchmirror/web-<number><!-- web-pr:<number> -->markerandroid: skipprevents a new mirrorCHAT_ANDROID_PR_TOKENand runs onself-hostedANDROID.md) mentions the mirror workflow in one short paragraphReferences
.gitea/workflows/unit-tests.yml(pull_request,self-hosted)chat_android/docs/SCREEN_INVENTORY.md