Update
CI / scan_ruby (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled

This commit is contained in:
2026-04-29 00:57:55 +01:00
parent 6f64e1a530
commit b66ee00361
8 changed files with 119 additions and 109 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ class AdminsController < ApplicationController
else
session[:admin_unlocked] = false
@unlocked = false
flash.now[:alert] = "That passphrase did not unlock anything. Check the advert again."
flash.now[:alert] = "That passphrase did not unlock anything. Check the stock page again."
render :show, status: :unprocessable_entity
end
end
+27 -25
View File
@@ -13,10 +13,12 @@ class ListingsController < ApplicationController
stock_number: "FC-718-421",
exterior: "Graphite Blue Metallic",
interior: "Black leather with chalk stitching",
seller_name: "Amelia Bennett",
seller_role: "Verified private seller",
seller_since: "On Forecourt since 2019",
response_time: "Usually replies within 14 minutes",
dealer_name: "Forecourt Specialist Cars",
dealer_tagline: "Indoor showroom · Appointment-led viewings",
dealer_since: "Established 2012",
dealer_contact: "01483 905210",
dealer_hours: "Mon-Sat 09:00-17:30",
dealer_address: "Woodbridge Meadows, Guildford",
image_caption: "Photo set 03 // ingest note JHDU // something seems shifted.",
hero_image: "https://images.unsplash.com/photo-1494976388531-d1058494cdd8?auto=format&fit=crop&w=1600&q=80",
gallery: [
@@ -27,11 +29,11 @@ class ListingsController < ApplicationController
highlights: [
"Original paint-depth readings documented in the gallery",
"Fresh Michelin PS4S tyres with under 1,500 miles",
"Annual brake fluid and gearbox service invoices retained from the seller's specialist"
"Annual brake fluid and gearbox service invoices retained in the vehicle file"
],
overview: [
"This Cayman S was specced exactly the way enthusiasts usually hope to find one: manual gearbox, Sport Chrono, PASM, and no unnecessary aero add-ons. The seller has owned it for four years, used it as a weekend car, and kept every service invoice in order.",
"Cosmetically it presents like a well-kept private sale rather than a detail-heavy showroom car. There are two tiny stone marks on the nose, the front splitter shows light road wear, and the cabin leather has stayed impressively matte.",
"This Cayman S was specced exactly the way enthusiasts usually hope to find one: manual gearbox, Sport Chrono, PASM, and no unnecessary aero add-ons. Forecourt acquired it from a long-term local owner and has kept the supporting service file in order.",
"Cosmetically it presents like an honestly prepared specialist-stock car rather than an over-restored showroom piece. There are two tiny stone marks on the nose, the front splitter shows light road wear, and the cabin leather has stayed impressively matte.",
"Forecourt's intake notes show an HPI-clear history, no insurance loss markers, two keys, the original order paperwork, and recent borescope images from the last annual inspection."
],
specs: [
@@ -45,13 +47,13 @@ class ListingsController < ApplicationController
[ "Keepers", "2 keepers" ],
[ "Vehicle history", "HPI clear, no outstanding finance" ]
],
document_packet: [
{ source: "Original order form", detail: "Factory spec confirmed against supplied paperwork", file_type: "PDF" },
{ source: "Annual alignment sheet", detail: "Alignment printout tucked behind service invoices", file_type: "Scan" },
{ source: "Battery conditioner leaflet", detail: "Charger notes only, no service relevance", file_type: "JPG" },
{ source: "Paint protection card", detail: "XPEL warranty card added to the folder", file_type: "Scan" },
{ source: "Key handover note", detail: "Leather sleeve re-dyed to match the cabin", file_type: "Scan" },
{ source: "Roadside kit insert", detail: "Emergency cartridge dated and photographed", file_type: "Scan" }
factory_options: [
{ code: "AAG", option: "Aluminium look fuel cap", verified_from: "Order form" },
{ code: "P04", option: "Automatically dimming mirrors", verified_from: "Photo match" },
{ code: "XLS", option: "Xenon headlamps with PDLS", verified_from: "Order form" },
{ code: "QR5", option: "Sport Chrono display surround", verified_from: "Prep sheet" },
{ code: "LTH", option: "Leather steering column casing", verified_from: "Order form" },
{ code: "EPK", option: "Extended interior package", verified_from: "Order form" }
],
condition_notes: [
"Cold-start video archived from 24 April",
@@ -59,18 +61,18 @@ class ListingsController < ApplicationController
"Underbody photos show no corrosion bloom",
"Driver's bolster has only light creasing"
],
seller_notes: [
"Always warmed through before spirited driving",
"No track days, no winter salt exposure",
"Super unleaded only, documented with fuel log",
"Includes factory battery conditioner and indoor cover"
dealer_notes: [
"Two keys, tracker cards and handbook pack present",
"Fresh MOT issued ahead of photography",
"Geometry check filed with the vehicle history pack",
"Viewings by appointment in the indoor showroom"
],
prep_tickets: [
{ task: "Bay card print", stamp: "2026-04-24 08:31 BST", code: "PDI-24-09", status: "Open" },
{ task: "Handover pack filed", stamp: "2026-04-24 08:27 BST", code: "PDI-24-11", status: "Closed" },
{ task: "Road test sign-off", stamp: "2026-04-24 08:21 BST", code: "PDI-24-18", status: "Closed" },
{ task: "Alarm fob check", stamp: "2026-04-24 08:16 BST", code: "PDI-24-01", status: "Closed" },
{ task: "Paint-depth sheet scanned", stamp: "2026-04-24 08:12 BST", code: "PDI-24-16", status: "Closed" }
inspection_log: [
{ note: "Front splitter edge photographed", stamp: "2026-04-24 08:31 BST", code: "OBS-17-09", status: "Advisory" },
{ note: "Battery conditioner case checked", stamp: "2026-04-24 08:27 BST", code: "INS-88-52", status: "Filed" },
{ note: "Road test complete", stamp: "2026-04-24 08:21 BST", code: "INS-88-73", status: "Filed" },
{ note: "Alarm fob battery confirmed", stamp: "2026-04-24 08:16 BST", code: "INS-88-21", status: "Filed" },
{ note: "Paint-depth sheet attached", stamp: "2026-04-24 08:12 BST", code: "INS-88-71", status: "Filed" }
]
}
@@ -1,13 +1,16 @@
import { Controller } from "@hotwired/stimulus"
const REVIEW_PATH = "/internal/pdi-bundle-7c4f"
export default class extends Controller {
static targets = ["hintPanel", "progress"]
static targets = ["hintPanel", "progress", "reviewPath"]
connect() {
this.clickCount = 0
console.info("[Forecourt QA] Local review tools remain mounted at /admin.")
console.debug("[Forecourt QA] One payload on this page is encoded for transport, not encrypted.")
if (this.hasReviewPathTarget) {
this.reviewPathTarget.textContent = REVIEW_PATH
}
}
tapLogo() {
+14 -11
View File
@@ -7,7 +7,7 @@
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-amber-400">Forecourt</p>
<h1 class="mt-1 text-2xl font-semibold text-white">Local admin console</h1>
</div>
<%= link_to "Back to advert", root_path, class: "inline-flex items-center rounded-full border border-white/15 px-4 py-2 text-sm font-medium text-zinc-200 transition hover:border-white/30 hover:text-white" %>
<%= link_to "Back to vehicle", root_path, class: "inline-flex items-center rounded-full border border-white/15 px-4 py-2 text-sm font-medium text-zinc-200 transition hover:border-white/30 hover:text-white" %>
</div>
</header>
@@ -35,19 +35,19 @@
<div class="mt-8 grid gap-4 md:grid-cols-2">
<button class="rounded-2xl border border-rose-400/35 bg-rose-500/10 px-5 py-4 text-left transition hover:bg-rose-500/15">
<p class="text-sm font-semibold text-rose-100">Delete adverts</p>
<p class="text-sm font-semibold text-rose-100">Delete stock page</p>
<p class="mt-1 text-sm text-rose-200/80">Soft-delete every draft older than 30 days.</p>
</button>
<button class="rounded-2xl border border-amber-400/35 bg-amber-500/10 px-5 py-4 text-left transition hover:bg-amber-500/15">
<p class="text-sm font-semibold text-amber-100">Ban Seller</p>
<p class="mt-1 text-sm text-amber-200/80">Suspend an account and freeze outbound messages.</p>
<p class="text-sm font-semibold text-amber-100">Mark reserved</p>
<p class="mt-1 text-sm text-amber-200/80">Hold a vehicle against inbound showroom enquiries.</p>
</button>
<button class="rounded-2xl border border-sky-400/35 bg-sky-500/10 px-5 py-4 text-left transition hover:bg-sky-500/15">
<p class="text-sm font-semibold text-sky-100">Rewrite price cache</p>
<p class="mt-1 text-sm text-sky-200/80">Force a fresh estimate against stale market comparables.</p>
</button>
<button class="rounded-2xl border border-fuchsia-400/35 bg-fuchsia-500/10 px-5 py-4 text-left transition hover:bg-fuchsia-500/15">
<p class="text-sm font-semibold text-fuchsia-100">Feature advert</p>
<p class="text-sm font-semibold text-fuchsia-100">Feature vehicle</p>
<p class="mt-1 text-sm text-fuchsia-200/80">Pin a vehicle to the top rail for 24 hours.</p>
</button>
</div>
@@ -62,15 +62,15 @@
</div>
<div class="mt-5 grid gap-4 md:grid-cols-2">
<label class="flex items-center justify-between rounded-2xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-zinc-300">
Spotlight seller badges
Spotlight stock badges
<input type="checkbox" checked disabled class="h-4 w-4 rounded border-zinc-700 bg-zinc-900 text-emerald-400">
</label>
<label class="flex items-center justify-between rounded-2xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-zinc-300">
Quiet suspicious offers
Quiet duplicate enquiries
<input type="checkbox" checked disabled class="h-4 w-4 rounded border-zinc-700 bg-zinc-900 text-emerald-400">
</label>
<label class="flex items-center justify-between rounded-2xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-zinc-300">
Auto-hide low-effort adverts
Auto-hide thin stock pages
<input type="checkbox" disabled class="h-4 w-4 rounded border-zinc-700 bg-zinc-900 text-emerald-400">
</label>
<label class="flex items-center justify-between rounded-2xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-zinc-300">
@@ -90,8 +90,8 @@
<p class="mt-1 text-zinc-400">Ran 18 minutes ago by `ops-preview`.</p>
</div>
<div class="rounded-2xl border border-white/10 bg-zinc-950/50 p-4">
<p class="font-medium text-white">Manual seller review</p>
<p class="mt-1 text-zinc-400">Awaiting notes from trust-and-safety.</p>
<p class="font-medium text-white">Manual stock review</p>
<p class="mt-1 text-zinc-400">Awaiting sign-off from the prep team.</p>
</div>
<div class="rounded-2xl border border-white/10 bg-zinc-950/50 p-4">
<p class="font-medium text-white">Homepage merchandising slot 02</p>
@@ -114,9 +114,12 @@
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-zinc-400">Restricted access</p>
<h2 class="mt-2 text-3xl font-semibold text-white">Admin login required</h2>
<p class="mt-3 text-sm leading-6 text-zinc-300">
Enter the four-word access phrase from the advert page in the format
Enter the four-word access phrase from the stock page in the format
<span class="rounded bg-white/10 px-2 py-1 font-mono text-xs text-zinc-100">WORD_1-WORD_2-WORD_3-WORD_4</span>.
</p>
<p class="mt-2 text-sm text-zinc-500">
Use the words in page order.
</p>
<%= form_with url: admin_path, method: :post, class: "mt-8 space-y-5" do |form| %>
<div>
+35 -34
View File
@@ -1,8 +1,6 @@
<% content_for :title, "#{@listing[:title]} | Forecourt" %>
<div data-controller="listing-puzzle" class="min-h-screen bg-zinc-50 text-zinc-900">
<!-- Forecourt QA: local review tools still live at /admin. -->
<div data-listing-puzzle-target="hintPanel" class="fixed bottom-5 right-5 z-50 hidden w-[22rem] rounded-3xl border border-zinc-200 bg-white p-5 shadow-2xl shadow-zinc-900/15">
<div class="flex items-start justify-between gap-4">
<div>
@@ -12,7 +10,8 @@
<button type="button" data-action="listing-puzzle#dismissHint" class="rounded-full border border-zinc-200 px-2.5 py-1 text-sm text-zinc-500 transition hover:border-zinc-300 hover:text-zinc-700">Close</button>
</div>
<p class="mt-3 text-sm leading-6 text-zinc-600">
The advert has clues in captions, checklists, page data, and repeating IDs. Devtools will help, and the local-only review screen still answers at <span class="font-mono text-xs text-zinc-900">/admin</span>.
The stock page has clues in captions, checklists, page data, and repeating IDs. Devtools will help, and one local-only review path is still wired into the page controller:
<span data-listing-puzzle-target="reviewPath" class="font-mono text-xs text-zinc-900"></span>
</p>
<p class="mt-3 text-xs text-zinc-500">Logo taps tracked: <span data-listing-puzzle-target="progress">0/5</span></p>
</div>
@@ -23,12 +22,12 @@
<span class="flex h-9 w-9 items-center justify-center rounded-full bg-zinc-950 text-sm font-semibold text-white">M</span>
<span>
<span class="block text-sm font-semibold text-zinc-950">Forecourt</span>
<span class="block text-xs text-zinc-500">Curated enthusiast adverts</span>
<span class="block text-xs text-zinc-500">Specialist sports and GT stock</span>
</span>
</button>
<div class="hidden items-center gap-3 md:flex">
<span class="rounded-full border border-emerald-200 bg-emerald-50 px-3 py-1 text-xs font-medium text-emerald-700">Verified seller</span>
<span class="rounded-full border border-emerald-200 bg-emerald-50 px-3 py-1 text-xs font-medium text-emerald-700">Dealer approved</span>
<span class="rounded-full border border-zinc-200 px-3 py-1 text-xs font-medium text-zinc-600">Price reduced this week</span>
</div>
</div>
@@ -38,7 +37,7 @@
<section class="border-b border-zinc-200 pb-10">
<div class="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
<div>
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-zinc-500">Advert FC-718-421</p>
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-zinc-500">Stock FC-718-421</p>
<h1 class="mt-2 text-4xl font-semibold tracking-tight text-zinc-950"><%= @listing[:title] %></h1>
<p class="mt-3 max-w-3xl text-sm leading-6 text-zinc-600"><%= @listing[:subtitle] %></p>
</div>
@@ -92,8 +91,8 @@
</div>
<div class="mt-6 space-y-3">
<button class="inline-flex w-full items-center justify-center rounded-full bg-zinc-950 px-5 py-3 text-sm font-semibold text-white transition hover:bg-zinc-800">Reserve for 24 hours</button>
<button class="inline-flex w-full items-center justify-center rounded-full border border-zinc-200 px-5 py-3 text-sm font-semibold text-zinc-900 transition hover:border-zinc-300 hover:bg-zinc-50">Message seller</button>
<button class="inline-flex w-full items-center justify-center rounded-full bg-zinc-950 px-5 py-3 text-sm font-semibold text-white transition hover:bg-zinc-800">Book a viewing</button>
<button class="inline-flex w-full items-center justify-center rounded-full border border-zinc-200 px-5 py-3 text-sm font-semibold text-zinc-900 transition hover:border-zinc-300 hover:bg-zinc-50">Request video walkaround</button>
</div>
<dl class="mt-6 grid gap-4 border-t border-zinc-200 pt-6 text-sm text-zinc-600">
@@ -116,12 +115,14 @@
</dl>
<div class="mt-6 border-t border-zinc-200 pt-6">
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-zinc-500">Seller</p>
<p class="mt-2 text-lg font-semibold text-zinc-950"><%= @listing[:seller_name] %></p>
<p class="mt-1 text-sm text-zinc-600"><%= @listing[:seller_role] %></p>
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-zinc-500">Dealership</p>
<p class="mt-2 text-lg font-semibold text-zinc-950"><%= @listing[:dealer_name] %></p>
<p class="mt-1 text-sm text-zinc-600"><%= @listing[:dealer_tagline] %></p>
<div class="mt-4 space-y-2 text-sm text-zinc-600">
<p><%= @listing[:seller_since] %></p>
<p><%= @listing[:response_time] %></p>
<p><%= @listing[:dealer_since] %></p>
<p><%= @listing[:dealer_hours] %></p>
<p><%= @listing[:dealer_address] %></p>
<p class="font-medium text-zinc-900"><%= @listing[:dealer_contact] %></p>
</div>
</div>
</div>
@@ -163,20 +164,20 @@
</div>
<div>
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-zinc-500">Document packet</p>
<p class="mt-3 text-sm leading-6 text-zinc-600">The seller uploaded the original paperwork bundle and a few newer scans. Check the details carefully.</p>
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-zinc-500">Factory options</p>
<p class="mt-3 text-sm leading-6 text-zinc-600">The stock sheet is longer than the original order form. Check the verified items carefully.</p>
<div class="mt-4 overflow-hidden rounded-3xl border border-zinc-200 bg-white">
<div class="grid grid-cols-[minmax(0,0.9fr)_minmax(0,1.3fr)_5.5rem] gap-4 border-b border-zinc-200 px-5 py-3 text-xs font-semibold uppercase tracking-[0.18em] text-zinc-500">
<p>Source</p>
<p>Detail</p>
<p class="text-right">File</p>
<div class="grid grid-cols-[5.5rem_minmax(0,1.35fr)_8rem] gap-4 border-b border-zinc-200 px-5 py-3 text-xs font-semibold uppercase tracking-[0.18em] text-zinc-500">
<p>Code</p>
<p>Option</p>
<p class="text-right">Verified</p>
</div>
<% @listing[:document_packet].each do |entry| %>
<div class="grid grid-cols-[minmax(0,0.9fr)_minmax(0,1.3fr)_5.5rem] gap-4 border-b border-zinc-100 px-5 py-4 text-sm last:border-b-0">
<p class="font-medium text-zinc-900"><%= entry[:source] %></p>
<p class="text-zinc-700"><%= entry[:detail] %></p>
<% @listing[:factory_options].each do |entry| %>
<div class="grid grid-cols-[5.5rem_minmax(0,1.35fr)_8rem] gap-4 border-b border-zinc-100 px-5 py-4 text-sm last:border-b-0">
<p class="font-mono text-xs font-semibold tracking-[0.18em] text-zinc-500"><%= entry[:code] %></p>
<p class="text-zinc-900"><%= entry[:option] %></p>
<div class="flex justify-end">
<span class="rounded-full border border-zinc-200 bg-zinc-50 px-3 py-1 text-xs font-medium text-zinc-600"><%= entry[:file_type] %></span>
<span class="<%= entry[:verified_from] == 'Order form' ? 'border-emerald-200 bg-emerald-50 text-emerald-700' : 'border-zinc-200 bg-zinc-50 text-zinc-600' %> rounded-full border px-3 py-1 text-xs font-medium"><%= entry[:verified_from] %></span>
</div>
</div>
<% end %>
@@ -186,33 +187,33 @@
<section class="grid gap-12 border-b border-zinc-200 py-10 lg:grid-cols-[minmax(0,1fr)_minmax(18rem,0.9fr)]">
<div>
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-zinc-500">Inspection notes</p>
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-zinc-500">Condition summary</p>
<div class="mt-4 grid gap-3 sm:grid-cols-2">
<% @listing[:condition_notes].each do |note| %>
<div class="rounded-2xl border border-zinc-200 bg-white px-4 py-4 text-sm text-zinc-700"><%= note %></div>
<% end %>
</div>
<p class="mt-8 text-xs font-semibold uppercase tracking-[0.24em] text-zinc-500">Seller notes</p>
<p class="mt-8 text-xs font-semibold uppercase tracking-[0.24em] text-zinc-500">Dealer notes</p>
<div class="mt-4 space-y-3 text-sm leading-6 text-zinc-700">
<% @listing[:seller_notes].each do |note| %>
<% @listing[:dealer_notes].each do |note| %>
<p><%= note %></p>
<% end %>
</div>
</div>
<div>
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-zinc-500">Pre-sale prep tickets</p>
<p class="mt-3 text-sm leading-6 text-zinc-600">The prep desk export is still sorted by latest activity. Closed tickets all share the same base code, and only one part changes.</p>
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-zinc-500">Inspection notes</p>
<p class="mt-3 text-sm leading-6 text-zinc-600">The workshop printout is still shown newest first. Only the notes filed into the final inspection bundle share the same base code, and the service desk phone still uses the old multi-tap keypad labels.</p>
<div class="mt-4 divide-y divide-zinc-200 rounded-3xl border border-zinc-200 bg-white">
<% @listing[:prep_tickets].each do |entry| %>
<% @listing[:inspection_log].each do |entry| %>
<div class="flex items-center justify-between gap-4 px-5 py-4">
<div>
<p class="text-sm font-medium text-zinc-900"><%= entry[:task] %></p>
<p class="text-sm font-medium text-zinc-900"><%= entry[:note] %></p>
<p class="mt-1 text-sm text-zinc-500"><%= entry[:stamp] %></p>
</div>
<div class="flex items-center gap-3">
<span class="<%= entry[:status] == 'Closed' ? 'border-emerald-200 bg-emerald-50 text-emerald-700' : 'border-amber-200 bg-amber-50 text-amber-700' %> rounded-full border px-3 py-1 text-xs font-medium"><%= entry[:status] %></span>
<span class="<%= entry[:status] == 'Filed' ? 'border-emerald-200 bg-emerald-50 text-emerald-700' : 'border-amber-200 bg-amber-50 text-amber-700' %> rounded-full border px-3 py-1 text-xs font-medium"><%= entry[:status] %></span>
<code class="rounded-full bg-zinc-950 px-3 py-1.5 text-xs text-zinc-100"><%= entry[:code] %></code>
</div>
</div>
@@ -224,9 +225,9 @@
<section class="py-10">
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-zinc-500">Forecourt promise</p>
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-zinc-500">Forecourt promise</p>
<p class="mt-2 max-w-3xl text-sm leading-6 text-zinc-600">
Every enthusiast advert includes verified vehicle-history data, recent visual documentation, and just enough intake weirdness to remind you a human touched it somewhere along the way.
Every car in stock includes verified vehicle-history data, recent visual documentation, and just enough intake weirdness to remind you a human touched it somewhere along the way.
</p>
</div>
<div class="text-sm text-zinc-500">
+1 -1
View File
@@ -11,5 +11,5 @@ Rails.application.routes.draw do
root "listings#show"
get "listings/graphite-cayman-s", to: "listings#show", as: :listing
resource :admin, only: %i[show create destroy], controller: "admins"
resource :admin, path: "internal/pdi-bundle-7c4f", only: %i[show create destroy], controller: "admins"
end
+32 -31
View File
@@ -8,35 +8,34 @@ This file is for the puzzle owner only and should not be linked from the UI.
## Route
- Hidden route: `/admin`
- Discovery points on the advert page:
- HTML comment near the top of the page source
- Browser console message from the Stimulus controller
- Optional hidden hint drawer after clicking the Forecourt logo 5 times
- Hidden route: `/internal/pdi-bundle-7c4f`
- Discovery points on the stock page:
- Stimulus controller source for the stock page (`listing_puzzle_controller`)
- Optional hidden hint drawer after clicking the Forecourt logo 5 times (the drawer renders the controller's stored path at runtime)
## Stage 1
- Location: main image caption on the advert page
- Location: main image caption on the stock page
- Visible clue: `Photo set 03 // ingest note JHDU // something seems shifted.`
- Solve method: Caesar shift back by 3
- Answer: `GEAR`
## Stage 2
- Location: "Document packet" table
- Visible hint: `The seller uploaded the original paperwork bundle and a few newer scans. Check the details carefully.`
- Location: "Factory options" table
- Visible hint: `The stock sheet is longer than the original order form. Check the verified items carefully.`
- Solve method:
- use only the rows marked `Scan`
- then take the first letter of each entry in the `Detail` column
- Alignment printout tucked behind service invoices
- XPEL warranty card added to the folder
- Leather sleeve re-dyed to match the cabin
- Emergency cartridge dated and photographed
- use only the rows verified from `Order form`
- then take the first letter of each option name in top-to-bottom order
- Aluminium look fuel cap
- Xenon headlamps with PDLS
- Leather steering column casing
- Extended interior package
- Answer: `AXLE`
## Stage 3
- Location: hidden JSON blob in the advert page source
- Location: hidden JSON blob in the stock page source
- Element ID: `forecourt-media-manifest`
- Encoded value: `VFVSQk8=`
- Solve method: Base64 decode
@@ -44,26 +43,28 @@ This file is for the puzzle owner only and should not be linked from the UI.
## Stage 4
- Location: "Pre-sale prep tickets" card
- Visible hint: `The prep desk export is still sorted by latest activity. Closed tickets all share the same base code, and only one part changes.`
- Ticket codes:
- `PDI-24-09` (`Open`, ignore this row)
- `PDI-24-11`
- `PDI-24-18`
- `PDI-24-01`
- `PDI-24-16`
- Location: "Inspection notes" card
- Visible hint: `The workshop printout is still shown newest first. Only the notes filed into the final inspection bundle share the same base code, and the service desk phone still uses the old multi-tap keypad labels.`
- Note references:
- `OBS-17-09` (`Advisory`, ignore this row)
- `INS-88-52`
- `INS-88-73`
- `INS-88-21`
- `INS-88-71`
- Solve method:
- use only the `Closed` tickets
- the card is shown newest first, so read the closed tickets from oldest to newest
- map the changing numeric suffixes with A=1
- 16 = P
- 01 = A
- 18 = R
- 11 = K
- use only the notes marked `Filed`
- ignore the one advisory entry with a different base code
- the card is shown newest first, so read the filed notes from oldest to newest
- decode the final two digits as classic mobile keypad `key + tap count`
- 71 = P (`7` pressed once)
- 21 = A (`2` pressed once)
- 73 = R (`7` pressed three times)
- 52 = K (`5` pressed twice)
- Answer: `PARK`
## Admin behavior
- `GET /admin` shows the login prompt
- `GET /internal/pdi-bundle-7c4f` shows the login prompt
- The login prompt tells solvers to use the words in page order
- Entering `GEAR-AXLE-TURBO-PARK` unlocks the fake admin panel
- The panel is session-backed only and can be cleared with the "Clear admin session" button
+3 -3
View File
@@ -1,14 +1,14 @@
require "test_helper"
class MotorlotFlowTest < ActionDispatch::IntegrationTest
test "advert page renders puzzle listing" do
test "stock page renders puzzle listing" do
get root_path
assert_response :success
assert_includes response.body, "2021 Porsche 718 Cayman S"
assert_includes response.body, "JHDU"
assert_includes response.body, "VFVSQk8="
assert_includes response.body, "PDI-24-16"
assert_includes response.body, "INS-88-71"
end
test "admin unlock succeeds with the full password" do
@@ -19,7 +19,7 @@ class MotorlotFlowTest < ActionDispatch::IntegrationTest
assert_response :success
assert_includes response.body, "Admin Panel Unlocked"
assert_includes response.body, "Delete adverts"
assert_includes response.body, "Delete stock page"
end
test "admin unlock rejects invalid passwords" do