Skip to content

Release: develop -> main - #31

Merged
TaprootFreak merged 2 commits into
mainfrom
develop
Sep 9, 2026
Merged

Release: develop -> main#31
TaprootFreak merged 2 commits into
mainfrom
develop

Conversation

@github-actions

@github-actions github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Automatic Release PR

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

Commits: 1 new commit(s)

Checklist

  • Review all changes
  • Verify CI passes
  • Approve and merge when ready for production

JonnyLuca and others added 2 commits September 9, 2026 02:52
* Add invite and promo landing pages.

* Add Playwright visuals for spent invite and promo landings.

* Add English Playwright visuals for invalid invite and promo landings.

* Add English Playwright visuals for spent invite and promo landings.

* Add English Playwright visuals for unavailable invite and promo landings.

* Add Playwright visuals for invite and promo landings with no code.

* Add English Playwright visuals for invite and promo with no code.

* Add loading mock and Playwright visuals for invite and promo landings.

* Add English Playwright visuals for invite and promo loading states.

* Add Android Playwright visuals for invite and promo loading states.

* Add Android Playwright visuals for invalid invite and promo landings.

* Add Android Playwright visuals for unavailable invite and promo landings.

* Add Android Playwright visuals for spent invite and promo landings.

* Add Android Playwright visuals for invite and promo with no code.

* Add fallback mock and Playwright visuals for nameless invite and promo landings.

* Add English Playwright visuals for nameless invite and promo landings.

* Add Android Playwright visuals for nameless invite and promo landings.

* Add English Android Playwright visuals for invite and promo loading states.

* Add English Android Playwright visuals for invalid invite and promo landings.

* Add English Android Playwright visuals for unavailable invite and promo landings.

* Pin referral lookup name fields

* test(web): refresh visual baselines

* fix(referral): harden invite/promo landing per review

EN: Escape reflected referral codes at the crawler HTML text sinks and use a
function replacer so a $ in the code is never a backreference; treat a
plain-text "Cannot GET" 404 as unavailable (read the body as text, not JSON);
render the promo action text verbatim (do not trim the displayed value); drop
the Android debug-keystore fingerprint from the production Digital Asset Links;
widen the English visual matrix to tablet and mobile with regenerated baselines.

DE: Reflektierte Referral-Codes an den Crawler-HTML-Textstellen escapen und per
Funktions-Replacer einsetzen (ein $ im Code ist nie mehr eine Backreference);
einen Klartext-404 "Cannot GET" als unavailable behandeln (Body als Text statt
JSON lesen); Promo-Aktionstext 1:1 anzeigen (Anzeige nicht trimmen); den
Android-Debug-Keystore-Fingerprint aus den Prod-assetlinks entfernen; die
englische Visual-Matrix auf Tablet und Mobile erweitern (Baselines neu erzeugt).

* test(web): widen remaining English visual views to tablet and mobile

EN: Extend the loading/invalid/unavailable English invite and promo views to
tablet-chromium and mobile-safari (completing the language x device x state
matrix) and add the regenerated baselines.

DE: Die englischen loading/invalid/unavailable-Views fuer Invite und Promo auf
tablet-chromium und mobile-safari erweitern (Matrix Sprache x Geraet x Zustand
vollstaendig) und die neu erzeugten Baselines ergaenzen.

* fix(referral): address review findings on the landing hardening

EN: Treat only a non-2xx text body as a message so a 200 with a non-JSON body
(a misconfigured proxy/CDN) falls through to the safe "unavailable" state
instead of a false-positive success page; add escaping tests for the image-alt
and description share sinks and a browser-mirror escaping test; add an e2e
regression for the 200-non-JSON case; document the deliberate escape-contract
difference between the server and browser replaceTaggedAttr mirrors; drop the
now-single debug fingerprint sentence from the README; and remove a stray
"(orchestrator)" suffix from three test names.

DE: Nur einen non-2xx-Textbody als Message behandeln, damit ein 200 mit
Nicht-JSON-Body (fehlkonfigurierter Proxy/CDN) in den sicheren
"unavailable"-Zustand faellt statt in eine falsch-positive Erfolgsseite;
Escaping-Tests fuer die image-alt- und description-Share-Sinks sowie einen
Browser-Mirror-Escaping-Test ergaenzen; eine e2e-Regression fuer den
200-Nicht-JSON-Fall hinzufuegen; die bewusste Escape-Vertragsdifferenz zwischen
Server- und Browser-replaceTaggedAttr-Mirror dokumentieren; den nun einzelnen
Debug-Fingerprint-Satz aus dem README entfernen; und ein verirrtes
"(orchestrator)"-Suffix aus drei Testnamen loeschen.

* test(referral): strengthen the landing-hardening regression tests

EN: Make the escaping assertions per-sink (count the escaped code once per
og/twitter/meta sink instead of a single global check) so a per-sink regression
is caught; add the missing quote-escape assertion and image-alt coverage to the
browser-mirror test; and assert the stubbed route was actually hit in the
200-non-JSON e2e test so it cannot pass via the generic network-error path.

DE: Die Escaping-Assertions sink-spezifisch machen (den escapten Code je
og/twitter/meta-Sink einmal zaehlen statt einer globalen Pruefung), damit eine
Regression je Sink auffaellt; die fehlende Quote-Escape-Assertion und
image-alt-Abdeckung im Browser-Mirror-Test ergaenzen; und im 200-Nicht-JSON-e2e
pruefen, dass die gestubbte Route wirklich getroffen wurde, damit er nicht ueber
den generischen Netzwerkfehler-Pfad besteht.

* fix(invite): stop folding issued codes and foreign paths onto a code

Three ways a wrong referrer could be credited:

- unwrapNestedCode matched `invite/` anywhere, so an issued code containing
  it (`PREFIXINVITE/AB12CD`) collapsed onto its tail. Codes may legitimately
  contain a slash and are not character-validated, so this hit honest users.
  Anchored at start or slash, on both the browser and the function mirror.
- codeFromReferralHostInPath accepted our host from any segment of a foreign
  path, so `example.com/r/realunit.app/invite/ATTACKER` yielded ATTACKER.
  Restricted to the marked viewer forms that need it: /amp/s/<host>,
  /c/s/<host> and /https/<host>.
- The function-side landing matcher was unbounded, so `evilrealunit.app`
  contained our host as a substring and passed.

Two tests asserted the first and second behaviours as intended and were
corrected; the attack case is now a regression test.

* test(coverage): gate the code the Pages Function actually runs

coverage.include only listed public/js/lib, so the 100% badge was earned by
a browser-side mirror whose two HTML transformers are referenced from tests
only, while functions/lib/itunes-banner.js — the module the middleware
imports — was measured not at all. It sits at 98.98% lines / 88% branches;
that is now a ratchet, not a blind spot.

Adds tests for three paths this uncovered (bare store link, unparseable
lang, fragment-only code) and drops a dead scheme-less retry in the
host-in-path unwrap.

* test(invite): assert values where the suite only asserted shape

Four places passed without checking the thing under test: three parser
fallbacks accepted any non-empty string, the cap test accepted any
32-character output, the Play-referrer expectation was built from the same
production helper it verifies, and a setAttribute stub discarded the value
it was called with.

* docs: describe the architecture the repo actually has

Three ground rules contradicted the code they govern: public/ does not ship
verbatim for invite/promo (the middleware rewrites those bytes), "no external
resources of any kind (…, fetch)" sits next to the API lookup the CSP
explicitly allows, and the visual matrix listed three families while VIEWS
carries six across 72 views.

* fix(invite): hand the app the kind the API confirmed

The page switched its copy on result.state but every hand-off kept using the
kind from the URL path, so /invite/<a promo code> showed the promo text while
the deeplink, the smart app banner and the Play install referrer still said
invite= — the app then processed it as the wrong programme.

The regression test fails without the fix.

* fix(invite): cap the code before the final strip and never split a surrogate

Capping ran after the trailing-punctuation strip, so a cut landing on a
separator left it in place (…C. instead of …C). A cut through a surrogate
pair left a lone surrogate, which makes encodeURIComponent throw when the
code goes into the lookup URL and the deeplink.

The 32-character cap itself stays: the API's sanitizeReferralCode and the
app fold identically, so rejecting here instead would put the three out of
step.

---------

Co-authored-by: Jonny Luca <320529100+JonnyLuca@users.noreply.github.com>
Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
* test(account-merge): cover empty otp, 404, DeadLetter and Failed jobs

The confirmation page already handles these API states; the suite now
opens each one the way a mail link would.

* test(account-merge): match sibling assertions and printWidth

Empty OTP also asserts the loading state is hidden. DeadLetter counts
the confirm request like the existing Failed case.

* test(account-merge): count the job GET on a Failed poll

A regression that skipped polling would still show unavailable. The
test now requires the confirm and the job request.

* test(account-merge): cover DeadLetter after a pending job poll

A ticket that is Pending on confirm and DeadLetter on the first job
GET must show unavailable and must have issued the job request.

---------

Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
@TaprootFreak
TaprootFreak merged commit 149d6c6 into main Sep 9, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants