Human overview · for understanding

Trip order consistency — the technical plan

From a proven bug to a one-checklist fix on the live balionline.hu WordPress site · 2026-06-26

From a proven bug to a one-checklist fix on the live balionline.hu WordPress site

Master summary — the gist in 30 seconds

TL;DRI confirmed the bug live: the same 10 trips appear in DIFFERENT orders on different pages, and none of those orders is by departure date. The plan fixes it at the source — one shared ordering helper that every trip list calls — verified by a small browser QA skill that already catches the bug today.

Input: the per-trip 'ranking number' the client already edits in the CMS. Output: that number honored identically on every trip list (collection page, menu dropdown, homepage, planner), proven by an automated order-comparison check across all surfaces.

Why this mattersA travel agency's revenue follows which trips are seen first. Today the collection page and the menu show the same trips in two different orders — so the client's single edit doesn't propagate, and the best trips get buried on some pages. Centralizing order in one place means one CMS edit reorders the whole site, and future trips behave automatically.
flowchart LR
  R["Ranking #<br/>(client edits once)"] --> H["ONE shared helper<br/>utazas_query_args()"]
  H --> A["Collection page ✅"]
  H --> B["Menu dropdown ✅"]
  H --> C["Homepage ✅"]
  H --> D["Planner / related ✅"]
  A --> G(["Same order<br/>everywhere"])
  B --> G
  C --> G
  D --> G

1 · The bug, proven live (not assumed)

TL;DRI fetched the real pages on 2026-06-26: the collection page lists the trips in one order, the menu dropdown lists the SAME trips in a different order, and neither is by date.

Input: the live HTML of /csoportos-utak-gyujtooldal and the homepage. Output: two provably different trip sequences for the identical 10 trips — e.g. a Sept-29 trip sits BELOW October trips on the collection page, and a 2027 trip sits mid-list.

Why it mattersPlanning on a guessed bug wastes the execution instance's time. Now the fix has a hard target and a hard proof: the QA skill's F4 check already FAILs on exactly this divergence, so we start from a real red.
flowchart TD
  T["Same 10 trips"] --> P1["Collection page<br/>amedet, 10ejsz, oszi, ..."]
  T --> P2["Menu dropdown<br/>amedet, yomo, 12ejsz, ..."]
  P1 --> X{"Equal?"}
  P2 --> X
  X -->|NO — different| BUG["This is the bug"]

2 · Cluster 1 — Backup, access & discovery (do FIRST)

TL;DRBefore any edit: full backup, whitelist our IP past the firewall, then read the live code to learn the exact ranking-field name and find EVERY place trips are listed.

Input: cPanel/FTP access (creds Matt holds). Output: a backup + a list of the exact field key and every trip-listing query, so the fix has no blind spots.

Why it mattersIt's a live booking site behind a cPanel firewall (admin returns 403 today). Two facts can't be learned remotely — the field's storage (ACF? menu_order? a meta key?) and the full set of listing queries — so they're discovery steps inside the checklist, gated behind a backup so nothing is risked.
flowchart LR
  BK["B1 Full backup<br/>files + DB"] --> WL["B2 Whitelist IP<br/>past cPGuard"]
  WL --> FF["B3 Find ranking<br/>field key+type"]
  WL --> EN["B4 Enumerate ALL<br/>listing queries"]
  FF --> READY(["Ready to fix"])
  EN --> READY

3 · Cluster 2 — The fix: one shared order, applied everywhere

TL;DRAdd ONE helper that defines the canonical order, then point every trip list at it — collection page, menu, homepage, planner, related trips.

Input: every trip-listing query found in discovery. Output: each one sorts by the ranking field (ascending, stable date tiebreak), with 'coming soon' placeholders pinned last.

Why it mattersOne source of truth beats per-page hacks. The moment all lists read the same helper, the client's single CMS edit propagates site-wide and new trips behave automatically. We reuse Dani's existing field — no new plugin to fight his code.
flowchart TD
  F1["F1 Shared helper<br/>utazas_query_args()"] --> F2["F2 Placeholders last"]
  F1 --> F3["F3 Collection page"]
  F1 --> F4["F4 Menu dropdown"]
  F1 --> F5["F5 Homepage"]
  F1 --> F6["F6 Planner / related /<br/>landing pages"]
  F3 --> OK(["All read ONE order"])
  F4 --> OK
  F5 --> OK
  F6 --> OK

4 · ADR-0001 — Why a shared helper, not a global hook

TL;DRWordPress's usual 'set a default order globally' trick (pre_get_posts) does NOT reach hand-coded loops like Dani's — so a shared helper function every loop calls is the right pattern here.

Input: a hand-built site with several custom WP_Query loops. Output: a tiny utazas_query_args() helper in a file Dani's deploy won't overwrite; each loop calls it instead of copy-pasting the sort.

Why it mattersThe global hook only affects the page's main query, not the secondary loops that actually render these grids — using it alone would silently fix nothing. Copy-pasting the sort into each loop recreates the exact drift that is this bug. One helper = one place to change, no drift, and a clean diff to hand back to Dani.
flowchart TD
  PG["pre_get_posts hook"] -.does NOT reach.-> L["hand-coded<br/>new WP_Query() loops"]
  H["Shared helper<br/>utazas_query_args()"] --> L
  L --> ONE(["One order,<br/>no drift"])

5 · ADR-0002 — Don't let a trip vanish

TL;DRIf the field is a meta key, the naive sort silently DROPS any trip that has no rank yet — a data-loss bug. The plan uses the safe pattern so unranked trips sort last instead of disappearing.

Input: a numeric ranking meta field where some trips may be blank. Output: a sort that includes ALL trips (blank ones last) and a check that the trip COUNT is unchanged before/after.

Why it mattersOn a booking site, a bookable trip quietly falling off a page is far worse than a wrong sort order. The safe 'left-join / not-exists' pattern guarantees no trip is ever hidden by the ordering change, and the count check catches a regression instantly.
flowchart LR
  N["Naive sort<br/>(inner join)"] --> DROP["Unranked trips<br/>DISAPPEAR ❌"]
  S["Safe sort<br/>(left join / NOT EXISTS)"] --> KEEP["Unranked trips<br/>sort LAST ✅"]
  KEEP --> CNT["Count unchanged<br/>= proof"]

6 · Cluster 3 + the QA skill — prove it, then lock the door

TL;DRA small read-only browser skill scrapes the trip order from every surface and asserts they're all equal; then we revert the temporary firewall opening and hand Dani a diff.

Input: the live URLs after the fix. Output: an automated 'all surfaces agree' check (desktop + mobile), a one-rank live mutation test, the firewall whitelist removed, and nothing committed that shouldn't be.

Why it mattersConsistency is the actual deliverable, so the acceptance gate compares surfaces to each OTHER, not just to a spec. The skill already works and FAILs on today's bug — so green genuinely means fixed. Reverting the IP allowance keeps the site's security posture intact afterward.
flowchart TD
  Q["balionline-order-qa<br/>scrape every surface"] --> EQ{"All orders equal?<br/>desktop + mobile"}
  EQ -->|no| FIX["Back to Cluster 2"]
  EQ -->|yes| MUT["C1 live rank-bump<br/>mirrors everywhere"]
  MUT --> REV["C2 revert firewall<br/>+ diff for Dani"]
  REV --> DONE(["Done"])

7 · One open choice (safe default already taken)

TL;DRRe-seed all ranks to date-order now, or keep the client's current values? Default = keep their values; only fix the surfaces. Re-seeding happens ONLY if Matt confirms.

Input: the client's existing rank numbers. Output: untouched by default (lowest risk, fully reversible); a separate optional step re-seeds them to date-ascending if you say so.

Why it mattersChanging the client's data is a bigger, less-reversible move than fixing code. Keeping their values means the fix is purely 'make every page obey the field' — zero risk to their content — while leaving the re-seed available as a one-line opt-in.
flowchart LR
  D{"Re-seed ranks<br/>to date order?"} -->|default: NO| KEEP["Keep client values<br/>fix surfaces only"]
  D -->|Matt confirms| SEED["Back up meta,<br/>then re-seed"]
  KEEP --> SAFE(["Reversible,<br/>no data change"])