Skip to content

🚀 Release: 1.9.4#515

Merged
jbourdin merged 20 commits intomainfrom
release/1.9.4
May 3, 2026
Merged

🚀 Release: 1.9.4#515
jbourdin merged 20 commits intomainfrom
release/1.9.4

Conversation

@jbourdin
Copy link
Copy Markdown
Owner

@jbourdin jbourdin commented May 3, 2026

Summary

  • Release 1.9.4 — see docs/changelog.md for details.

🤖 Generated with Claude Code

jbourdin and others added 20 commits May 3, 2026 11:43
Project doesn't use Stimulus or Turbo (no controllers.json, no
<turbo-frame>, no data-controller — frontend is React/Mantine +
Twig/Bootstrap). Both bundles were Flex-recipe leftovers that
Dependabot kept churning major-bump PRs against (#488, #489 closed).

Drops the composer packages, the four orphaned npm deps
(@hotwired/{stimulus,turbo}, @symfony/{stimulus-bridge,ux-turbo}),
config/packages/ux_turbo.yaml, the bundles.php entries, and the
turbo-track comment boilerplate in webpack_encore.yaml.

The subsequent `composer update` also picked up routine patch bumps:
Symfony 8.0.8 -> 8.0.9 across components, doctrine/persistence
4.1.1 -> 4.2.0, async-aws/sqs 2.8.1 -> 2.9.0, phpstan 2.1.54,
phpunit 13.1.8, aws-sdk-php 3.379.11, polyfills 1.37. No majors,
no security advisories.
All MESSENGER_TRANSPORT_*_DSN values default to doctrine:// in .env,
sync:// in .env.test, and docs/installation.md documents Doctrine as
the only supported transport. No sqs:// DSN, no AsyncAws/AmazonSqs
imports anywhere — the package was a Flex-recipe leftover.

Drops symfony/amazon-sqs-messenger and its only transitive deps
(async-aws/sqs, async-aws/core).

aws/aws-sdk-php stays — it's pulled by league/flysystem-aws-s3-v3
and used in EditorUploadStorageFactory and MosaicStorageFactory for
Scaleway S3.
Closes #467.

Adds vincentlanglet/twig-cs-fixer ^3.14 as a dev dep with the default
TwigCsFixer standard. New `.twig-cs-fixer.php` finder targets
templates/, gitignores the cache, and exposes two Make targets next to
the existing PHP fixers:

  make twig-cs-fix     # auto-fix template style
  make twig-cs-check   # dry-run for CI

Both wired into `lint-all`. CLAUDE.md pre-commit checklist + commands
table updated. CI workflow gains a "Twig-CS-Fixer (dry-run)" step right
after PHP-CS-Fixer in the lint+unit job.

Initial fix pass on 26 of the 103 templates: trailing commas in
multi-line arrays/hashes, unnecessary quotes on hash keys
(`{'_target_path': ...}` -> `{_target_path: ...}`), and `{% include %}`
tag converted to the `{{ include(...) }}` function (preferred since
Twig 2.x; same rendered HTML, cleaner scope handling). Functional tests
(982/982) confirm template rendering is unchanged.
…es.php

Flex rewrote config/bundles.php when unconfiguring the stimulus and
ux-turbo recipes, dropping the project's standard PHP header. CI's
PHP-CS-Fixer step caught it.
🔧 Chore: drop unused deps + add Twig-CS-Fixer + refresh deps
…nedCardController tests + sync service extensions

Refs #498. First batch of the F6.14 banned-card coverage backfill:

- BannedCardImageResolverTest (14 tests): all four URL-resolution
  branches — direct image URL, TCGdex CDN from TcgdexCard / parsed
  TcgdexId, PokemonTCG.io fallback, upstream-set-code fallback via
  TcgdexSet, plus rarity-tier sort, normalize-CDN dot-strip, all
  guessSerieIdFromSetId prefixes.
- BannedCardEnricherTest (11 tests): no-op when already linked, local
  hit, TCGdex API hit (with imageUrl-fill protection), alias fallback,
  all-null path; force-mode reset; reparent identity cache regression
  for two consecutive printings sharing one identity (placeholder
  removal verified); empty-name fill on existing canonical parent.
- AdminBannedCardControllerTest (12 tests): auth + role gates, active /
  history tabs, garbage view fallback, edit save round-trip, soft-delete
  + restore happy paths, CSRF rejection on delete and restore.
- BannedCardsSyncServiceTest extensions: empty-printings skip during
  soft-delete pass and the in-loop parentsByIdentityId cache that
  prevents duplicate parents across two same-identity entries in one
  sync run.
Refs #498. Second batch of the F6.14 banned-card coverage backfill,
finishing the priority list:

- BannedCardSeedDataTest (7 tests): applyTo fills nulls / preserves
  existing values / no-ops on unknown name; per-printing seeds for
  Unown LOT 90 vs LOT 91 (distinct ban dates); applyAll counts
  filled+skipped, single flush, skips when seed exists but all
  fields already filled.
- BannedCardsEnrichCommandTest (3 tests): linked / unresolved
  reporting, empty result, --force flag wired through.
- BannedCardsSeedCommandTest (2 tests): success and empty repo.
- AdminTechnicalControllerTest extension (4 tests): banned-cards-enrich
  auth + CSRF + happy path + force flag.
- BannedCardPrintingRepositoryTest (4 tests): findOneBySetCodeAndCardNumber
  hit/miss, findAllOrderedBySetAndNumber lex order + empty.
- CardPrintingRepositoryTest (3 tests): findFirstBySetCodeAndCardNumber
  prefers Expanded-legal then lowest rarity tier; null on miss.
- BannedCardFormTypeTest (4 tests): form binds to BannedCard,
  exposes all fields, valid payload round-trip, empty optional fields
  clear existing values.

Final and unit/functional totals: 1101 unit / 1005 functional tests
pass locally (was 1062 / 982 before this branch).
…ry suites

Two coverage fixes in one branch:

1. **Test suite registration**
   - Adds tests/Form and tests/Sentry to the unit suite in
     phpunit.xml.dist. Without them, BannedCardFormTypeTest (added in
     #509) and BeforeSendCallbackTest weren't running in CI.

2. **Sprite subsystem coverage** (F2.26 — was 0% before this PR)
   - SpriteResolverTest (10 tests): cache-hit short-circuit, CDN-then-
     PokeAPI fallback, no-CDN-config skip, exception swallow, data-URI
     encoding, isCached, in-memory pokedex-id memoization.
   - SpriteMappingSyncServiceTest (5 tests): inserts new + applies
     SLUG_ALIASES; updates pokedex id when changed; skips unchanged
     rows; throws on non-200 CSV fetch; CSV parser rejects empty,
     malformed, and zero-id lines.
   - SpritesSyncMappingCommandTest (2 tests): success path with counts,
     RuntimeException -> Command::FAILURE.
   - SpriteProxyControllerTest (3 tests): 404 on resolver miss, 200
     with image/png + content on cache hit, /api/sprites/slugs returns
     alphabetically-ordered JSON list.
   - PokemonSpriteMappingRepositoryTest (4 tests): findPokedexIdBySlug
     hit/miss, findAllSlugs ordering + empty.

3. **Source fix surfaced by the tests**
   - SpriteMappingSyncService.php: pass empty $escape to str_getcsv() —
     PHP 8.4+ deprecates the implicit default. Was hidden at 0%
     coverage; the new tests trigger the deprecation, which the
     project's failOnDeprecation=true would fail in CI.

Test totals: 1129 unit (was 1112), 1012 functional (was 1005).
✅ Tests: cover sprite subsystem + register Form & Sentry test suites
Two new functional test files extending the headline tests in the same
namespace (which only covered auth + a couple of happy paths).

AdminPageControllerCoverageTest (13 tests + 1 skip):
- list with q, category, channel filters; pagination beyond page 1
- reorder accepts JSON id list, rejects non-array payload (400)
- new page prefilled from channel + category query params
- new page submit creates row + redirects with success flash
- saveTranslation: existing locale update on welcome (en),
  new-locale creation on a freshly-persisted app-channel page (fr),
  rejects locale not in channel (404)
- delete + duplicate reject invalid CSRF (flash danger)
- duplicate clones every translation, sets isPublished=false, preserves
  noIndex + ogImage + menuCategory

AdminMenuCategoryControllerCoverageTest (14 tests):
- list with view=footer, view=garbage (falls back to menu),
  channel=app filter
- reorder accepts category-id array, returns {ok:true}
- new submit creates category + redirects to edit
- new with view=footer flags category as footer
- new with channel=app attaches the channel
- edit GET renders form; edit POST saves + redirects with flash
- saveTranslation: existing-locale update, new-locale creation on
  a fresh app-channel category, rejects locale not in channel (404)
- delete with valid CSRF removes the row, with invalid CSRF rejects
  with danger flash and keeps the row

Test totals: 1040 functional (was 1012), 1129 unit unchanged. Both
controllers were 28-46 % covered before this PR.
✅ Tests: backfill AdminPage and AdminMenuCategory controllers
…t 0%)

The naive "PDF generation is hard to test" framing missed the obvious
seam: both generators run a Twig render to produce HTML, then call
Dompdf on it. Stubbing Twig::render with willReturnCallback to capture
the template context lets us assert on every data-prep branch (grouping,
sorting, font-size auto-fit, name localization, set-symbol fetch, sprite
embedding) without booting the real templates. Dompdf still runs against
the captured-then-passed-through tiny HTML so renderPdf() is exercised
too; on a 6-line body it's effectively free (~5ms per test).

PdfDecklistGeneratorTest (15 tests):
- generateAnonymous / generatePersonal happy paths produce %PDF-* output
- mode flag, playerName, trigram, gravatar URI propagation
- gravatar 404 keeps gravatarDataUri null (graceful degradation)
- deck without current version still renders with empty groups
- card grouping + qty-desc-then-name-asc sort across all sections
- trainer fallback bucket when subtype is null
- font size shrinks under load, clamps to 9pt ceiling for tiny decks
- localized card name preferred when deck has a single language
- English subtitle added when display name differs from English name
- set-symbol .png fetch with content-type normalization
- HTTP exception during symbol fetch swallowed gracefully
- TcgdexCardRepository fallback path with in-memory caching
- multi-language deck falls back to English locale

PdfLabelGeneratorTest (9 tests):
- generate produces %PDF-* using the simple-label template
- QR code data URI + base URL extraction (scheme + host)
- sprite resolution + slug title-case (incl. dash → space conversion)
- sprite resolver returning null skips that sprite
- generateFoldable uses the foldable template
- foldable handles deck without current version (default 6pt size)
- foldable card grouping by subtype with fixed section ordering
- foldable font size shrinks for large decks, clamps to 7pt ceiling

Test totals: 1153 unit (was 1129).
…handler branches

Three coverage targets from the medium-tier list:

AdminTechnicalControllerCoverageTest (16 tests):
- enrich-retry / flush-reenrich auth + CSRF reject branches
- mosaic-generate CSRF reject + happy path (info/success flash)
- sprite-mapping-rebuild CSRF reject branch
- tcgdex-sync-insert / tcgdex-sync-update CSRF reject branches
- banned-cards-sync CSRF reject branch
- clear-cache CSRF reject + happy path
- clear-app-cache CSRF reject + happy path (cache->clear)
- clear-cache-key CSRF reject + empty-key warning + valid delete

Note: dispatch happy paths for Messenger-backed actions (set-mappings,
tcgdex-sync, flush-reenrich, banned-cards-sync, sprite-mapping-rebuild)
are not asserted because the test env runs sync transports and the
handlers reach external services (TCGdex / pokemon.com / PokeAPI)
without try/catch in the controller. The CSRF + auth branches are the
testable surface; the controller code beyond them is one-line dispatch.

DeckShowPdfRoutesTest (9 tests):
- /deck/{tag}/label.pdf returns application/pdf for owner; 403 otherwise
- /deck/{tag}/label-foldable.pdf same access pattern + valid PDF body
- /deck/{tag}/decklist.pdf personal mode for owner, ?anonymous=1 variant,
  403 for non-owner
- /deck/{tag}/re-enrich requires ROLE_TECHNICAL_ADMIN; invalid CSRF -> 403

GenerateMinifiedMosaicHandlerTest extensions (4 tests):
- Catch branch: exception inside the pipeline is logged at error level
  and rethrown
- MINIFIED_PRINTING_OVERRIDES static map short-circuits resolveMinifiedImage
  (GEN|73 -> XY 129 with no CardPrinting reference)
- Two cards with the same name + image collapse into one tile via the
  buildMergedTiles dedup keyed on `name|imageUrl`
- Tile sort order pokemon -> trainer -> energy with qty-desc + name-asc
  within each type

Test totals: 1133 unit (+4) / 1065 functional (+25).
✅ Tests: cover PDF decklist + label generators (319 LOC at 0%)
✅ Tests: AdminTechnical + DeckShow PDF + mosaic handler branches
Both fields were missing from the explicit form_row calls in the deck
new and edit templates, so form_end was emitting them at the bottom of
the form (after the submit button area in some layouts).

- new.html.twig: format goes right after name; latestSet sits between
  the languages island and the public checkbox (matching edit.html.twig).
- edit.html.twig: format added right after name. latestSet was already
  in place.
🐛 Fix: render format + latestSet at the right spot in deck forms
@jbourdin jbourdin merged commit 214be8f into main May 3, 2026
5 checks passed
@jbourdin jbourdin deleted the release/1.9.4 branch May 3, 2026 21:37
@sentry
Copy link
Copy Markdown

sentry Bot commented May 3, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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.

1 participant