Skip to content

Promote: staging -> develop#859

Merged
TaprootFreak merged 5 commits into
developfrom
staging
Jul 15, 2026
Merged

Promote: staging -> develop#859
TaprootFreak merged 5 commits into
developfrom
staging

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automatic Staging PR

This PR was automatically created after changes were pushed to staging.

Commits: 1 new commit(s)

Checklist

  • Review all changes
  • Verify CI passes
  • Approve and merge to promote into develop

TaprootFreak and others added 5 commits July 15, 2026 13:36
## Problem

Every push to `staging` currently runs the "RealUnit Build" workflow
twice on the same SHA:

1. once from the `push: staging` trigger, and
2. once from the permanently-open auto-PR "Promote: staging → develop"
(#841) via `pull_request`.

Those two runs use different concurrency groups (PR number vs.
`github.ref`), so `cancel-in-progress` never cancels one against the
other. Both compete for the single self-hosted runner slot used by
Visual Regression / `golden-tests`.

## Fix

Map that same-repo staging-head PR (`pull_request` + `head_ref ==
'staging'` + head repo matches this repository) into the same constant
concurrency group as `push: staging` (`refs/heads/staging`).
`cancel-in-progress` then cancels the duplicate, and exactly one real
run survives per staging-lane commit.

## Fail-closed (not skip-as-success)

This is deliberately **not** the skip-as-success approach previously
proposed and rejected in #850. A step/job that is merely skipped and
treated as green can mask a red `push: staging` result. Here there is no
`if:` skip and no success-without-running path: both triggers still
schedule real work; the concurrency group collapses the pair so one run
is cancelled. A red stays red. Worst case is a plain `cancelled` check —
fail-closed and fixable by re-running — never a masked green.

## Fork-identity guard

The group remapping requires
`github.event.pull_request.head.repo.full_name == github.repository`, so
a fork branch that happens to be named `staging` cannot cancel the real
staging lane.

## Feature PRs

Unaffected. They keep the existing PR-number concurrency group (with
`github.ref` fallback for push / workflow_dispatch).
…858)

## Summary

Follow-up to #854 (which fixed 25 sticky-CTA surfaces). An **empirical**
audit of the surfaces the original grep missed — they use
`Column(mainAxisSize: .min)`, not `Spacer()`, so no text search finds
them — proved **4 more bottom sheets** clip their CTA out of the
hit-test region at large system text scale (dead button, no red stripe
in release):

| Sheet | breaks at | impact |
|---|---|---|
| `sell_confirm_sheet` | text scale 2.0 | cannot confirm a sale |
| `sell_executed_sheet` | **~1.2** | success sheet not closable — it was
shown **without** `isScrollControlled`, so clamped to 9/16 of screen
height |
| `forgot_pin_bottom_sheet` | 2.0 | cannot reset the PIN |
| `enable_biometric_bottom_sheet` | 2.0 | cannot enable/skip biometrics
|

## What changed

- New `shrinkWrap` mode on `ScrollableActionsLayout`: sizes to content
up to the available height, scrolls the body past it, keeps the actions
pinned — so a short sheet is not forced to full height but never clips
its CTA. With `shrinkWrap:false` (default) the 25 screens from #854 are
**byte-identical** (their whole test suite re-run unchanged).
- The 4 sheets migrated to `ScrollableActionsLayout(shrinkWrap: true)`
bounded at 0.9× screen; `sell_executed`'s call site fixed to
`isScrollControlled: true`; `sell_confirm`'s info-row horizontal
overflow fixed (both label and value shrink-safe).
- Each sheet gated by a matrix test pumped through a **real**
`showModalBottomSheet` (so the screen-height bound is faithful); every
test verified to fail against the pre-fix code.
- Catalog now lists all **29** surfaces; `welcome_page` audited → safe
(scrolls). Docs state honestly that no further sticky-CTA surface of
this shape is known.

## Test plan

- [x] `flutter analyze` clean
- [x] `flutter test` — **4432 passed** incl. goldens (rebased on current
staging with #855/#856)
- [x] 3 sell-sheet goldens regenerated & visually inspected; pin sheets
render pixel-identical
- [ ] CI on this PR
…rmed-email quote gate (#860)

## Problem

A buy confirm that the share register rejects with the
`PrimaryEmailRequired` error code (HTTP 400) currently falls into
`BuyConfirmError.unknown` and shows the generic technical-problem
snackbar — the buyer gets no hint that an email confirmation is
outstanding. This is the error path a real buyer hit repeatedly last
week.

## Changes

- **Confirm path:** `BuyConfirmError.primaryEmailRequired` — mapped from
the API error code (never from message text), 503 precedence unchanged
and pinned by a test. The snackbar reuses the existing
`buyPaymentConfirmFailedAktionariat` copy, which describes exactly the
required action (check your inbox for the pending confirmation); no new
ARB keys.
- **Quote path (forward-compatible):** handles the upcoming
`PrimaryEmailNotConfirmed` quote error (`isValid:false`). The action
button routes to the KYC page — which auto-resolves to its confirm-email
step — instead of the email-capture flow, then re-fetches the quote.
Unreachable until the API starts emitting the code, so this PR is fully
backward-compatible.
- Tests: cubit mappings (incl. 503-precedence pin), state props,
snackbar copy, action-button routing (pushes KYC, not email capture),
plus golden coverage for the new gate state and snackbar.

## Merge order

This app change must be released **before** the API starts emitting
`PrimaryEmailNotConfirmed` — older app versions would otherwise degrade
to the generic retry dead-end on the quote screen. The confirm-path fix
is effective immediately with the current API.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
## Summary

- isolate staging-push routing from canonical required checks
- let the ready `staging → develop` PR own normal CI
- dispatch the full `RealUnit Build` as a fail-safe when the promotion
PR is missing, draft, stale, or cannot be queried
- preserve per-lane cancellation for obsolete SHAs without any
cross-event cancellation
- upgrade first-party Actions to Node.js 24-compatible majors and
disable the inapplicable Go module cache
- document trigger ownership and fallback guarantees

## Root cause

`push: staging` and the promotion PR previously shared one concurrency
group. When the PR event arrived after the push event,
`cancel-in-progress: true` cancelled the push run on the same head SHA.
GitHub attached the cancelled `Analyze & Test`, `Coverage Floor Gate`,
`Visual Regression`, and `BitBox quirks audit` checks to the current
promotion PR, leaving it visibly red even though the replacement PR run
succeeded.

## Design

Canonical jobs remain in `pull-request.yaml` with their stable
required-check names. That workflow now handles PRs, `push: develop`,
and manual dispatches only.

The new `staging-ci-fallback.yaml` workflow handles `push: staging`:

1. Query open `staging → develop` PRs using an exact base, head
owner/branch, repository, draft-state, and head-SHA match.
2. If a ready same-repository PR already points at the pushed SHA, do
nothing: its `synchronize` event owns canonical CI.
3. If the PR is missing, draft, stale, or the API lookup fails, dispatch
`pull-request.yaml` on `staging` via `workflow_dispatch`.
4. Keep router concurrency isolated from `RealUnit Build`, so routing
can never cancel required PR checks.

This removes duplicate heavy macOS/self-hosted work in the normal
promotion path while failing safe to a full build whenever PR ownership
cannot be proven.

## Required-check safety

The initial same-workflow approach was deliberately replaced after live
verification showed that GitHub renders expressions in skipped job names
literally. Keeping routing in a separate workflow means:

- canonical required-check names are stable
- the router never emits skipped checks with canonical names
- a skipped job cannot accidentally satisfy a required check
- no raw expression-based check names appear in the staging-push path

## Additional CI cleanup

- `actions/checkout@v6`
- `actions/setup-go@v6` with `cache: false` because this repository has
no `go.mod`/`go.sum`
- `actions/upload-artifact@v6`
- `actions/download-artifact@v7`

These versions use the Node.js 24 runtime. The self-hosted golden runner
reported version `2.335.1`, above the Actions minimum of `2.327.1`.

## Validation

- `actionlint -ignore 'label ".+" is unknown'
.github/workflows/pull-request.yaml
.github/workflows/staging-ci-fallback.yaml`
- Ruby YAML parse for both workflows
- `git diff --check`
- exact embedded router script exercised with mocked GitHub responses:
  - ready PR at current SHA → no fallback dispatch
  - draft PR → full fallback dispatch
  - stale PR SHA → full fallback dispatch
  - GitHub API failure → full fallback dispatch
  - manual invocation → full dispatch

The ignored `actionlint` diagnostics are the repository's existing
custom self-hosted labels (`m3-ultra`, `realunit-app`).

### End-to-end result

[Manual `RealUnit Build` run
29418489339](https://github.com/RealUnitCH/app/actions/runs/29418489339)
completed successfully on commit `69e86893`:

- `Analyze & Test` — success
- `Coverage Floor Gate` — success, including artifact upload/download on
the upgraded Actions
- `Visual Regression` — success on the self-hosted runner
- `BitBox quirks audit` — success without the previous `go.sum` cache
warning
- no Node.js 20 deprecation annotation
#862)

## Why

RealUnitCH/web#23 adds a dedicated confirm-page end state
(`no-registration`) with four new Playwright baselines (18 → 22 PNGs).
The handbook deploy stages those baselines behind the
`EXPECTED_WEB_BASELINE_COUNT` guard, which requires the count bump and
the matching gallery cards in the same change.

## Changes

- `handbook.yaml`: `EXPECTED_WEB_BASELINE_COUNT` 18 → 22.
- `docs/handbook/de/index.html` (`#spec-web`): four cards for the new
views (desktop de/en, tablet, mobile), mirroring the confirm-invalid
card group.

## Coupling

Merge together with RealUnitCH/web#23 — the guard fails the next
handbook deploy if either side lands alone. The PR-level `Handbook Build
Check` does not stage web baselines, so this PR's CI is unaffected
either way.
@TaprootFreak TaprootFreak added the tier3:full Opt-in: run Tier 3 Maestro handbook flows on this PR label Jul 15, 2026
@TaprootFreak
TaprootFreak merged commit 87f79b1 into develop Jul 15, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tier3:full Opt-in: run Tier 3 Maestro handbook flows on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant