diff --git a/.github/workflows/handbook-build-check.yaml b/.github/workflows/handbook-build-check.yaml index 415cdd57..e797fa30 100644 --- a/.github/workflows/handbook-build-check.yaml +++ b/.github/workflows/handbook-build-check.yaml @@ -1,8 +1,32 @@ name: Handbook Build Check -# PR-only build verification for Dockerfile.handbook + the Goldens-assembled -# screenshots. Does NOT push to Docker Hub and does NOT deploy — that -# remains the job of handbook-deploy.yaml (staging push → DEV, develop push → PRD). +# PR-only build verification for the handbook CONTENT. Gates four things +# before a staging merge, all pure content checks with no live deploy: +# - screenshot-assembly: scripts/assemble-handbook-screenshots.sh runs and +# produces the expected PNG count +# - store-listing sync: scripts/assemble-handbook-store-listing.py output +# matches the committed handbook (+ its HTML +# sanitizer strips injection payloads) +# - legal-sync: scripts/assemble-handbook-legal.py output +# matches the committed handbook +# - image build + container smoke: +# `docker build` (no push) + a container smoke +# test: /healthz answers and /de/ sits behind the +# auth wall. The HTTP probes do not establish +# per-file existence (the auth gate answers 401 +# before nginx ever looks a file up): screenshots +# are proven by their explicit count guard above, +# legal downloads by build-legal-downloads.sh's +# fail-fast per-base existence check during the +# image build +# +# Does NOT check out DFXswiss/api or RealUnitCH/web (those only happen in +# the real build inside handbook.yaml) and does NOT push to Docker Hub or +# deploy — that remains the job of handbook-deploy.yaml (staging push → +# PRD). The paths filter below mirrors the CONTENT inputs of the handbook +# image plus this workflow itself; the deploy WIRING (SSH, secrets, +# server rollout) are exercised exclusively by the deploy run, not by this +# check. # # Path filter covers everything that goes into the handbook image: # - docs/handbook/** handbook HTML, README, en/de subtrees diff --git a/.github/workflows/handbook-deploy.yaml b/.github/workflows/handbook-deploy.yaml index 83aa834a..5e417558 100644 --- a/.github/workflows/handbook-deploy.yaml +++ b/.github/workflows/handbook-deploy.yaml @@ -1,88 +1,51 @@ name: Handbook CI/CD -# Per-branch handbook deploy pipeline, one environment per branch: -# push to `staging` → build from staging → deploy DEV (dev-handbook.realunit.app) -# push to `develop` → build from develop → deploy PRD (handbook.realunit.app) +# Handbook deploy pipeline: a push to `staging` deploys directly to PRD +# (handbook.realunit.app). There is no separate DEV environment anymore — +# the former dev-handbook.realunit.app instance is deliberately +# decommissioned and no longer served. # -# DEV and PRD are independent runs keyed by the pushed branch; they no -# longer share a single run that fans out to both. The "DEV is green before -# PRD ships" guarantee is now provided by the branch-promotion flow itself: -# handbook content reaches `develop` only after it has been on `staging` -# (auto-staging-pr.yaml opens the staging → develop PR), i.e. after it was -# built and smoke-tested on DEV. Promoting to develop is what triggers PRD. -# -# This is a deliberate dev/prd-coupling exception scoped to the handbook -# alone — the wallet app itself still follows the strict develop→main -# separation. +# This also means the previous "DEV is green before PRD ships" guarantee is +# gone by design. The safety net for handbook CONTENT is now the PR check +# (handbook-build-check.yaml), which runs on every handbook-relevant PR — +# it gates screenshot-assembly, store-listing sync, legal-sync, and an +# image build + container smoke test, not the deploy WIRING itself (SSH, +# secrets, server rollout), which only the actual deploy run here +# exercises. There is no intermediate staging-only deploy left to catch +# problems before PRD sees them. # # All build/deploy/smoke logic lives in the reusable workflow -# .github/workflows/handbook.yaml. This file only wires the per-env -# parameters (source ref, image tag, smoke URL, deploy secrets) and routes -# the pushed branch to its environment via the per-job `if:` guards below. +# .github/workflows/handbook.yaml. This file only wires the PRD parameters +# (source ref, image tag, smoke URL, deploy secrets) and gates the single +# deploy job on the pushed branch via its `if:` guard below. on: push: - branches: [staging, develop] - paths: - - "docs/handbook/**" - - "test/goldens/**" - - "scripts/assemble-handbook-screenshots.sh" - - "scripts/assemble-handbook-store-listing.py" - - "scripts/templates/store-listing.html.tmpl" - # Store-listing section is baked into the image from the Fastlane - # metadata; a metadata- or screenshot-only change must redeploy too. - - "ios/fastlane/metadata/**" - - "ios/fastlane/screenshots/**" - - "android/fastlane/metadata/**" - - "Dockerfile.handbook" - - "handbook.nginx.conf" - - "handbook.htpasswd" - - ".github/workflows/handbook-deploy.yaml" - - ".github/workflows/handbook.yaml" + branches: [staging] workflow_dispatch: permissions: contents: read -# One concurrency lane PER BRANCH so a staging (DEV) deploy and a develop -# (PRD) deploy never serialize against — or cancel — each other. Within a -# single lane a manual workflow_dispatch still can't race a push: both would -# build the same env's tag in parallel, the later build would win on Docker -# Hub, and the target server could pull either depending on timing. -# cancel-in-progress is false so a mid-rollout deploy is never killed (we'd -# otherwise risk recreating the container halfway through). +# One concurrency lane (keyed by branch) so a push and a manual +# workflow_dispatch on the same branch never race — or cancel — each other: +# both would otherwise build the same :latest tag in parallel, the later +# build would win on Docker Hub, and the target server could pull either +# depending on timing. cancel-in-progress is false so a mid-rollout deploy is +# never killed (we'd otherwise risk recreating the container halfway +# through). concurrency: group: handbook-deploy-${{ github.ref_name }} cancel-in-progress: false jobs: - # Push to staging → DEV. Distinct image tag (:beta) from PRD (:latest) so a - # staging build can never clobber the develop build on Docker Hub (both - # used to push :beta, which raced whenever the two branches were pushed - # close together). - deploy-dev: - if: github.ref_name == 'staging' - uses: ./.github/workflows/handbook.yaml - with: - environment: DEV - ref: staging - docker_tag: dfxswiss/realunit-app-handbook:beta - smoke_url: https://dev-handbook.realunit.app/healthz - secrets: - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_DEV_SSH_KEY }} - DEPLOY_SSH_KNOWN_HOSTS: ${{ secrets.DEPLOY_DEV_SSH_KNOWN_HOSTS }} - DEPLOY_USER: ${{ secrets.DEPLOY_DEV_USER }} - DEPLOY_HOST: ${{ secrets.DEPLOY_DEV_HOST }} - - # Push to develop → PRD. + # Push to staging → PRD. deploy-prd: - if: github.ref_name == 'develop' + if: github.ref_name == 'staging' uses: ./.github/workflows/handbook.yaml with: environment: PRD - ref: develop + ref: staging docker_tag: dfxswiss/realunit-app-handbook:latest smoke_url: https://handbook.realunit.app/healthz secrets: diff --git a/.github/workflows/handbook.yaml b/.github/workflows/handbook.yaml index 578c5a87..b966850d 100644 --- a/.github/workflows/handbook.yaml +++ b/.github/workflows/handbook.yaml @@ -3,12 +3,15 @@ name: Handbook reusable CI/CD # Reusable workflow that builds the handbook nginx image, publishes it to # Docker Hub, and tells the target server to pull + recreate the container. # -# Caller (handbook-deploy.yaml) provides the per-environment parameters -# (source ref, tag, smoke URL, deploy secrets) so the deploy logic itself -# lives in exactly one place. Each pushed branch is an independent caller -# run — staging → DEV, develop → PRD — so there is no DEV→PRD `needs:` -# ordering here; "DEV green before PRD" comes from the staging→develop -# promotion flow (see handbook-deploy.yaml header). +# Caller (handbook-deploy.yaml) provides the PRD parameters (source ref, +# tag, smoke URL, deploy secrets) so the deploy logic itself lives in +# exactly one place. A push to staging deploys directly to PRD +# (handbook.realunit.app); the former DEV instance is retired. There is no +# DEV→PRD `needs:` ordering here — the PR check (handbook-build-check.yaml) +# gates handbook CONTENT (screenshot-assembly, store-listing sync, +# legal-sync, image build + container smoke) before a change can reach +# staging; it does not exercise this reusable workflow's deploy WIRING +# itself (SSH, secrets, server rollout) — see handbook-deploy.yaml header. # # Mirrors the shared SSH-deploy workflow templates from the internal ops repo. @@ -16,15 +19,15 @@ on: workflow_call: inputs: environment: - description: "Target environment label (dev | prd) — used in job name + smoke log lines" + description: "Target environment label (prd) — used in job name + smoke log lines" required: true type: string ref: - description: "Git ref of THIS repo to build the handbook from — the pushed branch (staging for DEV, develop for PRD). Determines the deployed handbook 'Stand'." + description: "Git ref of THIS repo to build the handbook from — staging (the sole deploy source; DEV is retired). Determines the deployed handbook 'Stand'." required: true type: string docker_tag: - description: "Full Docker image tag, e.g. dfxswiss/realunit-app-handbook:beta (DEV) / :latest (PRD)" + description: "Full Docker image tag, e.g. dfxswiss/realunit-app-handbook:latest (PRD)" required: true type: string smoke_url: @@ -63,25 +66,24 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - # Build the handbook from the environment's source branch: staging - # for DEV, develop for PRD (passed by handbook-deploy.yaml as the - # pushed branch). This ref governs the deployed handbook content; - # the DFXswiss/api checkout below is a SEPARATE, intentionally - # develop-pinned source for the mail previews and does not vary - # with this input. + # Build the handbook from staging — the only branch that deploys + # now (passed by handbook-deploy.yaml as the pushed branch; the + # former develop-triggered DEV/PRD split is retired). This ref + # governs the deployed handbook content; the DFXswiss/api checkout + # below is a SEPARATE, intentionally develop-pinned source for the + # mail previews and does not vary with this input. ref: ${{ inputs.ref }} # RealUnit mail previews are the single source of truth in DFXswiss/api # (scripts/generate-realunit-previews.js). We pull them in at build time # instead of checking them into this repo, so a change to a template or # i18n string in the api repo flows into the handbook image on the next - # build without a manual sync PR. A handbook-relevant push to staging - # (→DEV) or develop (→PRD) — or a manual workflow_dispatch on - # handbook-deploy.yaml in this repo — rebuilds the image and re-pulls - # api@develop here — so mail-template changes in the api repo only flow - # in after such a push/dispatch in this repo. There is NO auto-dispatch - # from the api repo. The api ref stays `develop` for both environments - # (it does not track the handbook's staging/develop split). + # build without a manual sync PR. A push to staging (→ PRD) — or a + # manual workflow_dispatch on handbook-deploy.yaml in this repo — + # rebuilds the image and re-pulls api@develop here — so mail-template + # changes in the api repo only flow in after such a push/dispatch in + # this repo. There is NO auto-dispatch from the api repo. The api ref + # stays `develop` regardless of the handbook's own deploy branch. - name: Check out DFXswiss/api at develop into _api-checkout/ uses: actions/checkout@v4 with: @@ -299,8 +301,8 @@ jobs: # balance artifacts above, we pull them in at build time instead of # committing them here, so a re-accepted web baseline flows into the # handbook image on the next build without a manual sync PR. The web ref - # stays `develop` for both environments (it does not track the handbook's - # staging/develop split) — mirroring the api@develop pin above: develop is + # stays `develop` regardless of the handbook's single deploy branch + # (`staging`) — mirroring the api@develop pin above: develop is # RealUnitCH/web's integration branch, i.e. the latest accepted baselines. # RealUnitCH/web is a PUBLIC repo, so the runner's default GITHUB_TOKEN is # enough to clone it (no cross-repo PAT). If it is ever flipped to private, diff --git a/README.md b/README.md index abdc459b..dd0c2b95 100644 --- a/README.md +++ b/README.md @@ -174,7 +174,7 @@ Tier 1 specs live under `test/integration/**` and run inside the same `flutter t | `auto-tag.yaml` | Push `develop` | Creates the next `vX.Y.Z` patch tag (PATCH = previous + 1, MINOR/MAJOR from pubspec floor) | | `release.yaml` | Tag `v*` · manual | Single store-release pipeline. Guard job routes by PATCH: `vX.Y.0` → production candidate (GitHub release, prerelease: false); `vX.Y.Z` (Z >= 1) → internal release (GitHub pre-release). Both lanes deploy Android + iOS to Play Internal + TestFlight; production promotion stays manual in the store backends. | | `store-metadata.yaml` | Push `main` under `*/fastlane/metadata/**` or `ios/fastlane/screenshots/**` · manual `workflow_dispatch` | Sync App Store + Play Store listing text + screenshots without rebuilding the app. A `preflight` gate rejects `FIXME-` placeholders and over-length text fields before either store upload runs. | -| `handbook-deploy.yaml` | Push `staging` (→ DEV) or `develop` (→ PRD) under `docs/handbook/**`, `Dockerfile.handbook`, `handbook.nginx.conf`, `handbook.htpasswd`, or the workflow files · manual | Builds the handbook image from the pushed branch and deploys it to the matching environment via the reusable `handbook.yaml`: `staging` → DEV (`:beta`, dev-handbook.realunit.app), `develop` → PRD (`:latest`, handbook.realunit.app). Independent per-branch runs with distinct image tags; "DEV green before PRD" is enforced by the staging→develop promotion flow, not an in-run `needs:` | +| `handbook-deploy.yaml` | Push `staging` · manual | Builds the handbook image from `staging` and deploys it to PRD via the reusable `handbook.yaml` (`:latest`, handbook.realunit.app). Content is pre-gated by `handbook-build-check.yaml` (screenshot-assembly, store-listing sync, legal-sync, and image build + container smoke); this workflow alone exercises the deploy WIRING (SSH, secrets, rollout) | | `handbook.yaml` | Called by `handbook-deploy.yaml` (`workflow_call`) | Reusable build → Docker Hub push → server pull/recreate → smoke check, parameterised per environment | ## Release versioning diff --git a/docs/handbook/README.md b/docs/handbook/README.md index a5b90307..e929bad8 100644 --- a/docs/handbook/README.md +++ b/docs/handbook/README.md @@ -17,7 +17,7 @@ open docs/handbook/de/index.html ``` Den gleichen Multi-Stage-Build macht `Dockerfile.handbook` automatisch beim -deployten Image (`handbook.realunit.app` / `dev-handbook.realunit.app`). +deployten Image (`handbook.realunit.app`). ## Screenshots regenerieren @@ -37,7 +37,7 @@ Workflow: `github-actions[bot]` zurück auf den Branch (siehe [`../visual-regression-tests.md`](../visual-regression-tests.md)) 4. Pullen → der nächste Handbook-Deploy zeigt die neue Baseline automatisch - (Push auf `staging` → DEV bzw. `develop` → PRD). + (Push auf `staging` → PRD). ## Selektive Läufe (Teilmenge) @@ -117,32 +117,33 @@ divergieren Image-Stand und Source-of-Truth. ### Trigger -Der Handbook-Deploy läuft pro Branch in genau ein Environment: ein `push` -auf `staging` deployt nach **DEV** (`dev-handbook.realunit.app`), ein `push` -auf `develop` nach **PRD** (`handbook.realunit.app`) — jeweils mit Änderungen -unter handbook-relevanten Pfaden (siehe `handbook-deploy.yaml`) oder per -manuellem `workflow_dispatch` auf `handbook-deploy.yaml` in **diesem** Repo -(das Ziel-Environment richtet sich nach dem dispatchten Branch). DEV und PRD -nutzen getrennte Image-Tags (`:beta` bzw. `:latest`), damit sich staging- und -develop-Builds nicht gegenseitig überschreiben. Eine reine Mail-Template-, -i18n- oder Generator-Änderung im api-Repo löst **keinen** automatischen -Rebuild aus — sie fliesst erst mit dem nächsten Handbook-Deploy hier rein. +Der Handbook-Deploy läuft in genau ein Environment (PRD): jeder `push` auf +`staging` deployt `handbook.realunit.app` (Image `:latest`) — es gibt keinen +Pfad-Filter mehr, jeder Merge nach `staging` löst den Deploy aus, unabhängig +davon, welche Dateien er ändert. Alternativ per manuellem `workflow_dispatch` +auf `handbook-deploy.yaml` in **diesem** Repo. Die frühere separate +DEV-Instanz (`dev-handbook.realunit.app`, Image `:beta`) ist stillgelegt und +wird nicht mehr bespielt; das Sicherheitsnetz für Handbook-INHALTE ist jetzt +der PR-Check `handbook-build-check.yaml`, der bei jedem handbook-relevanten +PR läuft (Screenshot-Assemblierung, Store-Listing-Sync, Legal-Sync sowie +Image-Build + Container-Smoke) — die Deploy-VERDRAHTUNG selbst (SSH/Secrets/ +Rollout in `handbook-deploy.yaml`) prüft erst der Deploy-Lauf selbst. Eine +reine Mail-Template-, i18n- oder Generator-Änderung im api-Repo löst +**keinen** automatischen Rebuild aus — sie fliesst erst mit dem nächsten +Handbook-Deploy hier rein. Wer eine reine Mail-Änderung sofort live haben will, hat zwei Optionen im realunit-app-Repo: ```bash -# Variante A: No-op-Touch unter einem handbook-relevanten Pfad, -# damit der path-Filter von handbook-deploy.yaml zieht. `--allow-empty` -# alleine reicht NICHT — der Push muss eine Datei unter docs/handbook/ -# (oder Dockerfile.handbook / handbook.nginx.conf / handbook.htpasswd / -# einen der beiden handbook-Workflows) tatsächlich anfassen. +# Variante A: No-op-Touch, dann push auf staging — es gibt keinen +# Pfad-Filter mehr, jeder Push auf staging löst den Deploy aus. touch docs/handbook/.sync && git add docs/handbook/.sync \ && git commit -m "chore(handbook): pull latest mail templates from api" \ - && git push origin develop + && git push origin staging # Variante B: manuell dispatchen (kein Commit nötig) -gh workflow run handbook-deploy.yaml --ref develop +gh workflow run handbook-deploy.yaml --ref staging ``` ### Lokal regenerieren @@ -236,15 +237,15 @@ Handbook-Build eingezogen: der Step "Stage web e2e baselines from web repo" in `tests/__screenshots__/{desktop-chromium,tablet-chromium,mobile-safari}/` nach `docs/handbook/web/` (gitignored). Single Source of Truth ist das web-Repo; die Ref bleibt — wie der api-Checkout — auf `develop` gepinnt (Integrations-Branch = -aktuell akzeptierte Baselines), unabhängig vom DEV/PRD-Ziel. +aktuell akzeptierte Baselines), unabhängig vom Deploy-Branch `staging`. Kommt im web-Repo eine View hinzu oder weg, failt der Build am `EXPECTED_WEB_BASELINE_COUNT`-Guard des Steps — dann die Zahl in `handbook.yaml` **und** die Bild-Karten in `#spec-web` (`docs/handbook/de/index.html`) im selben Zug anpassen. Eine reine Website-Änderung im web-Repo löst hier **keinen** automatischen Rebuild aus — sie fliesst erst mit dem nächsten Handbook-Deploy -rein (Push auf `staging` → DEV bzw. `develop` → PRD, oder manueller -`workflow_dispatch` auf `handbook-deploy.yaml`). +rein (Push auf `staging` → PRD, oder manueller `workflow_dispatch --ref staging` +auf `handbook-deploy.yaml`). ### Lokal ansehen diff --git a/docs/handbook/de/index.html b/docs/handbook/de/index.html index 4ef0d2dd..a91e0aba 100644 --- a/docs/handbook/de/index.html +++ b/docs/handbook/de/index.html @@ -476,6 +476,30 @@ flex-wrap: wrap; gap: 12px; } + .test .img.img-multi > div { + flex: 1 1 0; + min-width: 0; + text-align: center; + } + /* Must come after the unconditional base rules above (not inside the + earlier 700px media query near .tests.cols-2/.cols-3): two rules + with equal selector specificity resolve by source order, so an + override living in an earlier media-query block would otherwise + lose to this file's later, unconditional base rule regardless of + viewport width. */ + @media (max-width: 700px) { + .test .img.img-multi { + flex-direction: column; + align-items: center; + } + .test .img.img-multi > div { + flex: none; + width: 100%; + } + } + .test .img.img-multi > div img { + max-width: 100%; + } .test .img img { max-width: 220px; height: auto; @@ -1099,8 +1123,8 @@
handbook-deploy.yaml baut das Docker-Image aus dem gepushten Branch
und assembliert die 52 Slots aus test/goldens/screens/ → Push auf
- staging deployt das DEV-Image, Push auf develop das
- PRD-Image, beide via Cloudflare Tunnel.
+ staging deployt das Handbook-Image nach Produktion, via
+ Cloudflare Tunnel.
@@ -7983,6 +8007,27 @@ Anleitung:
+
+ Die Freischaltung bleibt dauerhaft auf dem Gerät erhalten (auch nach
+ App-Neustart) — sie wird lokal per SharedPreferences
+ gespeichert. Eine Neuinstallation der App setzt sie zurück.
+ OpenCryptoPay-Zahlungslinks funktionieren unabhängig von der
+ Freischaltung — der Deeplink bleibt bewusst immer offen. Tippen auf die
+ Versionsnummer nach erfolgter Freischaltung hat keine Wirkung mehr.
+
Nachher: alle vier Aktionen.
Anleitung:
-
- Die Freischaltung bleibt dauerhaft auf dem Gerät erhalten (auch nach
- App-Neustart) — sie wird lokal per SharedPreferences
- gespeichert. Eine Neuinstallation der App setzt sie zurück.
- OpenCryptoPay-Zahlungslinks funktionieren unabhängig von der
- Freischaltung — der Deeplink bleibt bewusst immer offen. Tippen auf die
- Versionsnummer nach erfolgter Freischaltung hat keine Wirkung mehr.
-
staging → DEV
- bzw. develop → PRD mit handbook-relevanten Pfaden oder
- manueller workflow_dispatch auf
+ mit dem nächsten Handbook-Deploy (Push auf staging → PRD,
+ oder manueller workflow_dispatch --ref staging auf
handbook-deploy.yaml im realunit-app-Repo) reingezogen.
Kommt im web-Repo eine View hinzu oder weg, failt der Handbook-Build am
EXPECTED_WEB_BASELINE_COUNT-Guard, bis Zahl und Karten hier
@@ -8524,9 +8547,8 @@ staging → DEV bzw.
- develop → PRD mit handbook-relevanten Pfaden oder
- manueller workflow_dispatch auf
+ Handbook-Deploy (Push auf staging → PRD, oder
+ manueller workflow_dispatch --ref staging auf
handbook-deploy.yaml im realunit-app-Repo) reingezogen.