May 17, 2026

Guaranteed Cabins, Quality Score Sanity, Faster /casino

Per-Sailing Casino Offer Flags

A follow-up to the casino mobile-endpoint switch from Wednesday.

After a closer look at fresh mobile-API responses, the per-sailing comp/gty/gobo booleans are populated -- they're just sparse. About 1,930 sailings in a recent 30-offer sample have is_gty=true, and they don't get the offer-level default applied. With the previous mapping, every Guaranteed sailing was coming through as is_gty=false and rendering as plain "for Two" instead of "for Two -- Guaranteed."

GTY sailings also ship their room-type list under a different field name (roomTypeListV2) than the rest (roomTypes). The previous mapping only read the first, so GTY rows lost their room type entirely and rendered with a "Room" fallback.

Both fixed -- Guaranteed sailings under a Complimentary offer now correctly show "for Two -- Guaranteed" and preserve their cabin tier. Room-type strings are also title-cased now ("Balcony" / "Ocean View" instead of "BALCONY" / "OCEANVIEW"), which had been causing the same logical cabin tier to render as two separate groups in the offer-detail view. The duplicate uppercase rows on prod were reconciled into their title-case siblings.

Quality Score: COMP and GTY Swapped

The casino offer quality score had been treating GTY higher than non-GTY COMP, which is backwards. GTY ("Guaranteed cabin") means RC picks the specific cabin within the category at their discretion -- potentially obstructed view, near elevators, deck nobody wants, etc. Both have the same dollar value for the cruise itself, but non-GTY has materially more value through cabin choice.

The scoring spread is unchanged (still a 30-point max on the Comp-status dimension), but the right side of the seesaw is now correct: COMP > GTY > GOBO. Affects the sort order on the /casino quality column and the casino analytics averages.

Faster /casino

The logged-in /casino HTML had ballooned to 7.86 MB decoded with a 6.3-second TTFB, because we were rendering every sailing for every active offer up front -- even though every offer-details panel started collapsed. 98% of the payload was inside hidden rows.

Each offer-details cell now renders an empty placeholder on initial page load and fetches the full sailings table from a dedicated endpoint on first click. The per-row port resolution, ship lookup, and tooltip markup vanishes from the initial render entirely. The page snaps in.

Cruise Account Auto-Pause on Auth Failure

If a linked account's password is wrong (HTTP 401/403 from the cruise line), we now pause the account, email you once, and stop trying. Before this, we had dead code that should have done this but was never wired up, so a broken-password account kept hammering RC's auth endpoint every sync cycle -- which can get the underlying account locked out at the cruise line.

Transient failures (5xx, network, 429) are treated separately and still retried as before. Only genuine credential rejection triggers the pause. The "Test Connection" button on the account page uses the same logic but skips the email, since you're sitting right there and will see the result on screen.

Bonus fix that fell out of the same change: the manual pause button had been silently broken in production for an unknown duration (a Postgres boolean-binding quirk where PHP false was coercing to empty string), so users who tried to pause an account via the UI weren't actually getting it paused. That works now.