9 Commits

Author SHA1 Message Date
semantic-release-bot
266fdc18a8 chore(release): 1.1.0 [skip ci]
# [1.1.0](https://gitea.1000co.fr/millianlmx/tabatago/compare/v1.0.3...v1.1.0) (2026-07-20)

### Features

* **home:** custom SVG zone icons for body-zone cards ([#15](#15)) ([8f10fe7](8f10fe763d))
2026-07-20 12:32:23 +00:00
8f10fe763d feat(home): custom SVG zone icons for body-zone cards (#15)
All checks were successful
Admin Web Docker / Admin Web Tests (push) Successful in 33s
CI / Detect Changes (push) Successful in 4s
Admin Web Docker / Docker Build Validation (push) Has been skipped
Admin Web Docker / Semantic Release (push) Successful in 10s
CI / YouTube Worker (push) Has been skipped
Admin Web Docker / Build & Push Docker Image (push) Successful in 53s
CI / Deploy (push) Has been skipped
2026-07-20 14:31:34 +02:00
semantic-release-bot
c5b2538a60 chore(release): 1.0.3 [skip ci]
## [1.0.3](https://gitea.1000co.fr/millianlmx/tabatago/compare/v1.0.2...v1.0.3) (2026-07-20)

### Bug Fixes

* **ci:** pr-iphone-deploy.yml fails to parse — indent python3 body inside run: | ([#14](#14)) ([ee2da59](ee2da592cc))
* **ci:** pr-iphone-deploy.yml fails to parse — indent python3 body inside run: | ([#14](#14)) ([e4b97b4](e4b97b4d14))
2026-07-20 07:52:11 +00:00
ee2da592cc fix(ci): pr-iphone-deploy.yml fails to parse — indent python3 body inside run: | (#14)
All checks were successful
Admin Web Docker / Admin Web Tests (push) Successful in 33s
Admin Web Docker / Semantic Release (push) Successful in 10s
CI / YouTube Worker (push) Has been skipped
CI / Detect Changes (push) Successful in 3s
Admin Web Docker / Docker Build Validation (push) Has been skipped
Admin Web Docker / Build & Push Docker Image (push) Successful in 1m1s
CI / Deploy (push) Has been skipped
2026-07-20 09:51:21 +02:00
e4b97b4d14 fix(ci): pr-iphone-deploy.yml fails to parse — indent python3 body inside run: | (#14)
Some checks failed
CI / Deploy (push) Has been cancelled
Admin Web Docker / Docker Build Validation (push) Has been cancelled
Admin Web Docker / Semantic Release (push) Has been cancelled
Admin Web Docker / Build & Push Docker Image (push) Has been cancelled
Admin Web Docker / Admin Web Tests (push) Has been cancelled
CI / Detect Changes (push) Has been cancelled
CI / YouTube Worker (push) Has been cancelled
2026-07-20 09:51:21 +02:00
semantic-release-bot
7a1a70af87 chore(release): 1.0.2 [skip ci]
## [1.0.2](https://gitea.1000co.fr/millianlmx/tabatago/compare/v1.0.1...v1.0.2) (2026-07-19)

### Bug Fixes

* **ci:** pr-iphone-deploy no longer self-merges on its own comment ([98f4f82](98f4f82db2)), closes [#6937a36](https://gitea.1000co.fr/millianlmx/tabatago/issues/6937a36) [#12](#12)
* **ci:** replace trigger-level paths: with dorny/paths-filter (Gitea ignores on.pull_request.paths) ([27f9c6b](27f9c6b7b6)), closes [#13](#13)
2026-07-19 20:17:03 +00:00
e2532a8136 Merge pull request 'fix(ci): pr-iphone-deploy no longer self-merges on its own comment' (#13) from fix/ci-lgtm-self-match into main
All checks were successful
CI / Detect Changes (push) Successful in 4s
Admin Web Docker / Docker Build Validation (push) Has been skipped
Admin Web Docker / Semantic Release (push) Successful in 12s
CI / YouTube Worker (push) Has been skipped
Admin Web Docker / Build & Push Docker Image (push) Successful in 53s
CI / Deploy (push) Has been skipped
Admin Web Docker / Admin Web Tests (push) Successful in 33s
Reviewed-on: #13
2026-07-19 22:16:12 +02:00
Millian Lamiaux
27f9c6b7b6 fix(ci): replace trigger-level paths: with dorny/paths-filter (Gitea ignores on.pull_request.paths)
All checks were successful
CI / Detect Changes (pull_request) Successful in 3s
CI / YouTube Worker (pull_request) Has been skipped
CI / Deploy (pull_request) Has been skipped
Gitea Actions does not reliably honor `on.pull_request.paths:` filters —
the workflow silently fails to trigger even when changed files match the
filter. PR #13 touches .github/workflows/pr-iphone-deploy.yml (explicitly
listed in the paths filter) and AGENTS.md, but only ci.yml fired.

The repo history shows this was already known: 65d85b6 'remplacer paths
trigger par dorny/paths-filter' moved to job-level filtering, but later
commits reverted to the trigger-level paths: block. The proven-working
pattern already lives in ci.yml (dorny/paths-filter@v3 in a 'changes' job).

Fix (mirror ci.yml):
- Remove trigger-level `paths:` from on.pull_request.
- Add a 'changes' job (ubuntu-latest, dorny/paths-filter@v3, single 'ios'
  filter covering tabatago-swift/** and the workflow itself).
- Gate build-deploy: needs: changes + if: needs.changes.outputs.ios == 'true'.
- Gate wait-approval: if: needs.build-deploy.result == 'success' so a
  filtered-out deploy doesn't launch a merge poll against an undeployed PR.

Everything else unchanged: concurrency, squash-merge, 2h timeout, the
self-merge fix from the previous commit on this branch (sentinel marker
+ python3 parser). This PR will self-validate: it touches the workflow
file, so the ios filter matches, so the deploy runs.
2026-07-19 22:05:28 +02:00
Millian Lamiaux
98f4f82db2 fix(ci): pr-iphone-deploy no longer self-merges on its own comment
All checks were successful
CI / Detect Changes (pull_request) Successful in 3s
CI / YouTube Worker (pull_request) Has been skipped
CI / Deploy (pull_request) Has been skipped
The build-deploy job posts a 'Prêt à tester' comment whose body literally
contains 'Reply **LGTM** pour merger' and 'Reply **KO**' as instructions to
the reviewer. wait-approval re-fetches ALL comments every 30s and greps any
body for \bLGTM\b (checked before KO) — so on the first poll (~30s after
deploy) the bot matched its OWN comment and squash-merged automatically,
with no human review. Regressed in #6937a36 (PR #12) which tightened the
regex to catch 'Tested, LGTM!' but didn't notice the bot body matched.

Fix (sentinel marker + python parse, identity-agnostic so it's safe whether
PR_API_TOKEN is a bot or a personal account):

- build-deploy: embed <!-- tabatago:ready-to-test --> in the bot comment
  (HTML comment, invisible in rendered markdown, present in raw body).
- wait-approval: replace the fragile grep-over-JSON with a python3 heredoc
  using json.loads + the existing \bLGTM\b / \bKO\b regexes. Skip any
  comment whose body contains the sentinel marker. Check KO BEFORE LGTM so
  a PR with both signals blocks (matches the existing 'KO blocks' intent).
  LGTM still does {"Do":"squash"}; KO still blocks; 2h timeout unchanged.

Verified locally with three scenarios:
  bot-only       -> PENDING  (was LGTM before — the bug)
  bot + reviewer -> LGTM     (squash-merge still fires)
  bot + KO       -> KO       (block still fires, even with bot LGTM in history)

AGENTS.md §5 step 9, §5 pitfalls, and §8 anti-patterns updated so the doc
describes the marker rule and warns against re-introducing the self-match.
2026-07-19 21:14:59 +02:00
5 changed files with 158 additions and 26 deletions

View File

@@ -8,12 +8,11 @@ on:
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
# Only run the iOS build+deploy when the PR actually touches the Swift app
# or this workflow itself. Docs-only / backend-only / admin-web-only PRs
# skip the runner entirely (no point rebuilding an unchanged .app).
paths:
- 'tabatago-swift/**'
- '.github/workflows/pr-iphone-deploy.yml'
# NOTE: no trigger-level `paths:` here — Gitea Actions does not reliably
# honor `on.pull_request.paths:` filters (the workflow silently fails to
# trigger even when changed files match). Path gating is done at the job
# level via dorny/paths-filter@v3 in the `changes` job below (same pattern
# as ci.yml). Docs/backend-only PRs then skip the macOS runner.
# Every mutation (comments, merge) goes through the Gitea API with PR_API_TOKEN,
# never the runner's native GITHUB_TOKEN — so a `permissions:` block would be
@@ -26,8 +25,29 @@ concurrency:
jobs:
# ── Path filter — determines whether the macOS build+deploy is worth running ──
# Gitea Actions does not honor trigger-level `on.pull_request.paths:`, so we
# gate at the job level with dorny/paths-filter (same pattern as ci.yml).
changes:
name: Detect Changes
runs-on: ubuntu-latest
outputs:
ios: ${{ steps.filter.outputs.ios }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
ios:
- 'tabatago-swift/**'
- '.github/workflows/pr-iphone-deploy.yml'
build-deploy:
name: Build & Deploy to iPhone (devicectl)
needs: changes
if: needs.changes.outputs.ios == 'true'
runs-on: macos
timeout-minutes: 30
@@ -172,19 +192,38 @@ jobs:
run: |
PR="${{ github.event.pull_request.number }}"
REPO="${{ github.repository }}"
# The HTML comment is invisible in Gitea's rendered markdown but is
# present in the raw body — wait-approval uses it as a sentinel to
# skip THIS bot comment when scanning for LGTM/KO (otherwise the
# instruction text "Reply LGTM pour merger" would self-trigger a
# merge ~30s after deploy). DO NOT remove or reword without also
# updating the scanner in the wait-approval job.
curl -s -X POST \
-H "Authorization: token ${GT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"body\":\"## 📱 Prêt à tester !\\n\\nL'app est déployée sur l'iPhone (devicectl).\\n\\n- Teste les changements\\n- Reply **LGTM** pour merger\\n- Reply **KO** pour bloquer\"}" \
-d "{\"body\":\"## 📱 Prêt à tester !\\n\\nL'app est déployée sur l'iPhone (devicectl).\\n\\n<!-- tabatago:ready-to-test -->\\n\\n- Teste les changements\\n- Reply **LGTM** pour merger\\n- Reply **KO** pour bloquer\"}" \
"${GITEA_URL}/api/v1/repos/${REPO}/issues/${PR}/comments"
wait-approval:
name: Wait for LGTM comment
needs: build-deploy
# Only run when build-deploy actually deployed. Without this, a skipped
# build-deploy (filtered out by `changes`) would still launch this job and
# the merge poll would run against a PR that was never deployed to device.
if: needs.build-deploy.result == 'success'
runs-on: macos
timeout-minutes: 120
steps:
- name: Checkout
# Shallow clone — wait-approval only needs scripts/pr_lgtm_scanner.py,
# nothing else from the repo. Same raw-git pattern as build-deploy's
# Checkout step (avoids actions/checkout setup).
run: |
git clone --depth 1 -b ${{ github.head_ref }} https://x-access-token:${PR_TOKEN}@gitea.1000co.fr/${{ github.repository }}.git .
env:
PR_TOKEN: ${{ secrets.PR_API_TOKEN }}
- name: Poll for LGTM
env:
GT_TOKEN: ${{ secrets.PR_API_TOKEN }}
@@ -202,6 +241,19 @@ jobs:
# Re-fetch ALL comments each cycle (not since_id) so we also catch
# edits — e.g. a reviewer changing "KO" → "LGTM". Negligible cost for
# PRs with <50 comments.
#
# The LGTM/KO decision is computed by scripts/pr_lgtm_scanner.py
# (a standalone Python file) rather than grep over raw JSON:
# - the bot's "Ready to test" comment body literally contains the
# words LGTM/KO as instructions to the reviewer, so any naive
# body grep would self-match and auto-merge ~30s after deploy.
# We skip it via the <!-- tabatago:ready-to-test --> sentinel
# embedded by the build-deploy job;
# - json.loads avoids false matches when the literal "body" key or
# trigger words appear inside another string field.
# KO is checked BEFORE LGTM so a PR with both signals blocks (matches
# the existing "KO blocks" intent — a reviewer who flip-flops shouldn't
# merge just because an older LGTM is still in the history).
while [ $TRIES -lt $MAX ]; do
sleep 30
@@ -210,23 +262,30 @@ jobs:
COMMENTS=$(curl -s -H "Authorization: token ${GT_TOKEN}" \
"${API}/issues/${PR}/comments?limit=50&page=1")
# Match LGTM/KO anywhere in the body (with a word boundary so "KOM"
# or "LGTMX" don't trigger). Case-insensitive.
if echo "$COMMENTS" | grep -qiE '"body":[[:space:]]*"[^"]*\bLGTM\b'; then
echo "✅ LGTM reçu ! Squash-merge..."
curl -s -X POST \
-H "Authorization: token ${GT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"Do\":\"squash\"}" \
"${API}/pulls/${PR}/merge"
echo "✅ Mergée (squash)."
exit 0
fi
# The LGTM/KO scanner lives in scripts/pr_lgtm_scanner.py rather
# than inline here. Embedding multi-line Python in a YAML `run: |`
# block scalar is fragile — YAML dedents block-scalar content to
# the first line, which produced both YAML parse failures and
# Python IndentationErrors in earlier iterations of this workflow.
# A standalone script sidesteps all of that and is testable locally.
DECISION=$(python3 scripts/pr_lgtm_scanner.py "$COMMENTS")
if echo "$COMMENTS" | grep -qiE '"body":[[:space:]]*"[^"]*\bKO\b'; then
echo "❌ KO reçu. Bloquée."
exit 1
fi
case "$DECISION" in
LGTM)
echo "✅ LGTM reçu ! Squash-merge..."
curl -s -X POST \
-H "Authorization: token ${GT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"Do\":\"squash\"}" \
"${API}/pulls/${PR}/merge"
echo "✅ Mergée (squash)."
exit 0
;;
KO)
echo "❌ KO reçu. Bloquée."
exit 1
;;
esac
if [ $((TRIES % 4)) -eq 0 ]; then
echo " ⏳ ... (${TRIES}/240, $(date +%H:%M))"

View File

@@ -167,7 +167,7 @@ Complications : `TabataGoComplication`.
6. **xcodegen generate**`xcodebuild -resolvePackageDependencies`**build** (`-scheme TabataGo`, Debug, auto-provisioning, team `2MJF39L8VY`). Le scheme build les 4 targets : `TabataGo` (app iOS), `TabataGoWidget` (widget iOS, vrai target — ne pas confondre avec `TabataGoWatchWidget` qui est watchOS), `TabataGoWatch` (app watchOS), `TabataGoWatchWidget` (complication watchOS).
7. **Deploy iPhone** UDID `00008120-000925CE3672201E` : `xcrun devicectl device install app` uniquement. `devicectl` gère nativement la découverte WiFi (réseau) et filaire (USB-C/Thunderbolt) — pas de fallback `ios-deploy`.
8. **Post comment** "Prêt à tester" sur la PR.
9. **Job `wait-approval`** : poll (30s, max 240 = 2h). **Re-fetche TOUS les comments** chaque cycle (pas `since_id`) pour attraper aussi les edits (un reviewer passant de "KO" à "LGTM"). Match LGTM/KO n'importe où dans le body (regex `\bLGTM\b` / `\bKO\b`, case-insensitive) — "Tested, LGTM!" compte ; "KOM" ne compte pas. `LGTM`**squash-merge** (`{"Do":"squash"}`). `KO` → blocage. Timeout → fail.
9. **Job `wait-approval`** : poll (30s, max 240 = 2h). **Re-fetche TOUS les comments** chaque cycle (pas `since_id`) pour attraper aussi les edits (un reviewer passant de "KO" à "LGTM"). Le parsing se fait en **python3** (`json.loads` + regex, pas de `grep` sur JSON brut). **Le commentaire bot "Prêt à tester" porte un marker sentinel `<!-- tabatago:ready-to-test -->`** et est **ignoré** par le scanner — sinon son propre body (qui mentionne LGTM/KO comme instructions au reviewer) déclencherait un auto-merge ~30s après le deploy (bug historique). KO est checké **avant** LGTM (un PR avec les deux signaux bloque). Match LGTM/KO n'importe où dans le body (regex `\bLGTM\b` / `\bKO\b`, case-insensitive) — "Tested, LGTM!" compte ; "KOM" ne compte pas. `LGTM`**squash-merge** (`{"Do":"squash"}`). `KO` → blocage. Timeout → fail.
### Secrets
@@ -187,6 +187,7 @@ Complications : `TabataGoComplication`.
- `-skipPackagePluginValidation -allowProvisioningUpdates`.
- **Concurrency** : le bloc `concurrency:` est volontaire — ne pas le retirer, sinon les pushes successifs empilent des pipelines et tentent des double-merges.
- **LGTM/KO regex** : matche n'importe où dans le body avec `\b...\b` (word boundary). Ne pas revenir à un `grep '"body": *"LGTM"'` ancré — il raterait "Tested, LGTM!".
- **Self-match sentinel** : le commentaire bot "Prêt à tester" contient littéralement `Reply **LGTM** pour merger` et `Reply **KO**` — sans précaution, le scanner matche son **propre** commentaire et auto-merge ~30s après le deploy. Le body porte donc un marker `<!-- tabatago:ready-to-test -->` que `wait-approval` ignore. **Tout commentaire bot posté par ce workflow doit porter ce marker** ; ne pas le retirer ni poster d'autre commentaire contenant LGTM/KO sans marker.
- **Squash-merge** (`{"Do":"squash"}`) — pas merge commit ni rebase. Garde l'historique `main` linéaire.
- **Comment edits** : `wait-approval` re-fetche tous les comments chaque cycle (pas `since_id`) pour attraper les edits.
@@ -249,6 +250,8 @@ Node.js (`server.js`, `package.json`, `Dockerfile`). Télécharge l'audio de pla
| Compter sur un bloc `permissions:` natif Gitea Actions | Tout passe par l'API Gitea avec `PR_API_TOKEN` ; le bloc natif serait no-op |
| Retirer le bloc `concurrency:` | Permet double-merge et pipelines empilés |
| Ancre le grep LGTM au début du body (`'"body": *"LGTM"'`) | Regex `\bLGTM\b` n'importe où dans le body (attrape "Tested, LGTM!") |
| Poster un commentaire bot dont le body contient "LGTM"/"KO" sans le marker `<!-- tabatago:ready-to-test -->` | Le scanner bot-scan ignore les comments portant le marker — tout commentaire bot du workflow doit l'inclure (sinon auto-merge en ~30s) |
| Scanner les comments en `grep` sur le JSON brut | `python3` + `json.loads` (gère unicode/quotes, pas de false match sur la clé `"body"`) |
| `{"Do":"merge"}` (merge commit) | `{"Do":"squash"}` pour un `main` linéaire |
| Supprimer le scheme explicite dans `project.yml` | xcodegen 2.45.4 n'en crée pas — scheme doit lister `TabataGo`/`TabataGoWidget`/`TabataGoWatch`/`TabataGoWatchWidget` |
| Compter sur `admin-web/` pour la app iOS | Dashboard admin séparé, communique via Supabase uniquement |

View File

@@ -1,6 +1,6 @@
{
"name": "my-app",
"version": "1.0.1",
"version": "1.1.0",
"private": true,
"scripts": {
"dev": "next dev",

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
"""LGTM/KO scanner for the pr-iphone-deploy workflow's wait-approval job.
Reads a Gitea PR comments JSON array (as returned by
``/api/v1/repos/{owner}/{repo}/issues/{pr}/comments``) on argv[1] and prints
exactly one of: ``LGTM``, ``KO``, ``PENDING``.
Rules
-----
* The bot's own "Ready to test" comment body literally contains the words
``LGTM`` and ``KO`` as instructions to the reviewer — without filtering it
out, the workflow would self-merge ~30s after deploy. Bot comments are
tagged with the sentinel ``<!-- tabatago:ready-to-test -->`` and skipped.
* ``KO`` is checked BEFORE ``LGTM`` — a PR with both signals blocks (matches
the existing "KO blocks" intent).
* Word-boundary regex (``\\bLGTM\\b`` / ``\\bKO\\b``, case-insensitive) so
"Tested, LGTM!" counts and "KOM" / "LGTMX" do not.
This script lives in ``scripts/`` (not inline in the workflow) because
embedding multi-line Python inside a YAML ``run: |`` block scalar is fragile:
YAML dedents block-scalar content to the first line, which produces either a
YAML parse failure or a Python ``IndentationError`` depending on how the
body is indented. A standalone file sidesteps all of that.
"""
import json
import re
import sys
MARKER = "<!-- tabatago:ready-to-test -->"
LGTM_RE = re.compile(r"\bLGTM\b", re.IGNORECASE)
KO_RE = re.compile(r"\bKO\b", re.IGNORECASE)
def decide(comments_json: str) -> str:
"""Return 'KO', 'LGTM', or 'PENDING' for the given comments JSON payload."""
try:
comments = json.loads(comments_json or "[]")
except Exception:
comments = []
hit_lgtm = False
hit_ko = False
for comment in comments:
body = comment.get("body") or ""
if MARKER in body:
# Skip the bot's own "Ready to test" comment — its body mentions
# LGTM/KO as instructions and would otherwise self-trigger.
continue
if KO_RE.search(body):
hit_ko = True
if LGTM_RE.search(body):
hit_lgtm = True
if hit_ko:
return "KO"
if hit_lgtm:
return "LGTM"
return "PENDING"
def main() -> int:
if len(sys.argv) != 2:
print("usage: pr_lgtm_scanner.py <comments_json>", file=sys.stderr)
return 2
print(decide(sys.argv[1]))
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,3 +1,3 @@
{
"version": "1.0.1"
"version": "1.1.0"
}