尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证正在加载完整 README。 以下为已保存的摘录。
A Next.js 15.5.21 App Router platform that gives UK travellers an evidence-backed second opinion
before they book journeys to Pakistan, India, the Gulf and Umrah, with supporting family-holiday
and business-class content.
Start with docs/project-control/ for the current production
baseline, completed-work ledger, active roadmap and decision log. It supersedes stale task-status
claims in older phase reports; JETSTASH_PRINCIPLES.md remains the authority for standing product
and architecture rules.查看完整 README ↗A Next.js 15.5.21 App Router platform that gives UK travellers an evidence-backed second opinion
before they book journeys to Pakistan, India, the Gulf and Umrah, with supporting family-holiday
and business-class content.
Start with docs/project-control/ for the current production
baseline, completed-work ledger, active roadmap and decision log. It supersedes stale task-status
claims in older phase reports; JETSTASH_PRINCIPLES.md remains the authority for standing product
and architecture rules.
tailwind.config.js)next/font/google/datanpm install
npm run dev
npx tsc --noEmit
npm run lint
npm test -- --run
npm run build
Open http://localhost:3000.
The GitHub repository and Vercel project are already connected:
main and verify the exact head commit and Preview result.main automatically triggers a Production
deployment; there is no separate manual promotion gate.jetstash.co.uk after an application change. For documentation-only changes, commit
and deployment identity are sufficient unless the build reports a problem.JetStash is live. Current status belongs in docs/project-control/, not in an accumulating launch
checklist. The following are either standing operating requirements or explicitly recorded
remaining work:
docs/project-control/ROADMAP.md. The implementation
system remains file-driven: add correctly named assets under /public/images/, regenerate
lib/image-manifest.json, and render through the shared brand-image components./app/api/subscribe/route.ts is wired to Brevo; the
required credentials are configured in Vercel Production. The form captures nearest-airport
and travel-interest preferences via the NEAREST_AIRPORT/TRAVEL_INTEREST attributes. All
seven required attributes (these two plus the five WATCH_* attributes below) were confirmed
in Brevo and live-tested end to end in July 2026. After an account change, run
npm run brevo:setup using the canonical list in lib/brevo-attributes.json; Brevo can silently
drop unrecognised attributes./app/api/contact/route.ts and /app/api/quote-request/route.ts use Resend and fail clearly
with 503 if RESEND_API_KEY is unavailable. CONTACT_TO_EMAIL is optional; both fall back to
siteConfig.contactEmail. Re-test after an API-route, provider or environment change rather
than repeating the complete audit routinely./data/deals.ts no longer carries a price at all — deal cards derive an honest range/single-check from /data/fare-observations.ts (getFareRangeSummary), or fall back to route facts when nothing's logged yet. The £ figures currently in /data/fare-observations.ts are still the original example numbers, not independently verified fares. Follow docs/project-control/FARE_OBSERVATION_ARCHIVE.md and append real, same-day checks over time as new FareObservation entries per route/cabin; never overwrite or retrospectively reconstruct an old observation.| Variable | Used by | Required for |
|---|---|---|
BREVO_API_KEY | /app/api/subscribe/route.ts, /app/api/route-watch/route.ts | Travel Club and Route Watch signups to save |
BREVO_LIST_ID | /app/api/subscribe/route.ts, /app/api/route-watch/route.ts | Destination list for those signups |
RESEND_API_KEY | /app/api/contact/route.ts, /app/api/quote-request/route.ts, /app/api/cron/fare-check-reminder/route.ts | Contact/quote delivery and weekly fare-check reminder |
CONTACT_TO_EMAIL | /app/api/contact/route.ts, /app/api/quote-request/route.ts, /app/api/cron/fare-check-reminder/route.ts | Optional — overrides siteConfig.contactEmail as the delivery address for contact/quote-request messages and the fare-check reminder |
CRON_SECRET | /app/api/cron/fare-check-reminder/route.ts | Required in Production. The endpoint fails closed with a 503 when it is absent or empty; Vercel Cron sends it as Authorization: Bearer <secret>. Never manually call production unless email side effects are controlled. |
app/ Routes (Next.js App Router)
page.tsx ISR homepage: Journey Desk / pre-booking second opinion
pakistan/ india/ gulf/ umrah/ Region hub pages
family-holidays/ business-class/ travel-club/
deals/ All fares, with client-side filtering
routes/[slug]/ Evidence-backed route guides and Route Status panels
travel-ready-check/ Passport/visa readiness tool
quote-request/ Umrah/family/group trip quote-request form
destinations/[slug] Dynamic destination pages (generateStaticParams)
airports/[slug] Dynamic airport pages (generateStaticParams)
api/subscribe/ Newsletter API route
api/contact/ Contact form API route
api/quote-request/ Umrah/family/group quote-request API route (Resend)
api/route-watch/ Per-route Route Watch signup API route (Brevo)
api/cron/ Weekly fare-check reminder
sitemap.ts Dynamic sitemap.xml
robots.ts robots.txt
components/
homepage-v2/ Public Journey Desk, pull interaction and Manchester→Mumbai visual
ui/ Button, Badge, DealCard, HubCard — shared primitives
layout/ Header, Footer
sections/ RouteMapHero, NewsletterSection, QuoteRequestForm, RegionHubPage template,
BookingMomentStrip, NextTravelMomentRibbon (Book-By Countdown — see below)
route/ warning-banner, route-timeline, fare-history-panel, booking-window-panel,
traveller-tip-list, community-notes-panel, route-watch-form, whatsapp-share-button,
book-by-countdown (the "when to book" panel — priority routes only)
data/
airports.ts UK departure airports
destinations.ts All destinations across every region
routes.ts Route guides plus pure Route Status derivation
route-status-events.ts Append-only sourced service-change ledger
deals.ts Deal-card curation metadata; deliberately no prices
airlines.ts Canonical airline reference (data/routes.ts links by slug)
peak-periods.ts Canonical demand-period reference (routes/destinations link by id)
peak-period-dates.ts Verified Gregorian dates per year for each peak period (Book-By Countdown)
route-timeline.ts Real, dated history of changes to specific routes
route-warnings.ts Append-only warning log per route
fare-observations.ts Append-only fare history per route (optional departureDate per entry)
booking-windows.ts Structured booking-window guidance, additive to route prose
airport-notes.ts Practical, hub-specific advice per airport
traveller-tips.ts Curated tips scoped to a route/destination/airport
community-notes.ts Real traveller-submitted notes (seeded empty — no submission pipeline yet)
lib/
site-config.ts Nav structure, region groupings, site metadata
brand-images.ts Real-photography resolver, backed by lib/image-manifest.json
route-status-copy.ts Evidence validation, public copy and effective presentation
flagship-status-copy.ts Homepage mapping from the canonical Route Status view model
booking-intelligence.ts Book-By Countdown's single derivation layer — see JETSTASH_PRINCIPLES.md §14
travel-intelligence-engine.ts Composes Book-By + route warnings into one readiness verdict — §14.2
utils.ts Tailwind className merge helper
quote-request-options.ts Shared trip-type/region options for the quote-request form + API route
Route pages (five priority routes only — see BOOK_BY_PRIORITY_ROUTE_SLUGS in
lib/booking-intelligence.ts) show a "When to book" panel: the next relevant festival/peak period,
a book-by date derived from that route's own stated booking-window guidance (or, where none exists,
the site's editorial planning assumption that fares on peak-period routes often move in the final
3–4 weeks), a visual timeline, the latest logged fare for context, and a state-dependent CTA
(check live price / Route Watch / honest urgency). Conservative planning guidance, never a fare
prediction and never a live-price claim — see JETSTASH_PRINCIPLES.md §14 for the full product
decision, §14.1 for the architecture, and §14.5 for the August 2026 evidence-safety correction that
removed unsupported certainty language ("expect a sharp jump" etc.) sitewide.
The panel also carries a small readiness-verdict badge ("Ready to book" / "Not yet" / "Check before
booking"...) — this is the customer-facing surface of lib/travel-intelligence-engine.ts, which
composes the booking-window state with any active route warning into one attributed answer to "Am I
ready to book?" (JETSTASH_PRINCIPLES.md §14.2). It's a priority decision tree, never a blended
score — every fact behind the badge stays individually visible.
The weekly workflow this depends on: for each priority route, manually check a fare on Google Flights, Trip.com or the airline's own site, then append a new data/fare-observations.ts entry with departureDate set to
the date you'd actually book for. /founder's "Book-By Countdown data cadence" and "Travel
Intelligence Engine — alert queue" sections (both nice-to-have, never a launch blocker — every
surface degrades honestly on its own) track what's due a check or worth a Route Watch send.
Colours, type and spacing are defined once in tailwind.config.js and app/globals.css — no inline hex values or magic numbers elsewhere in the codebase. If you need to adjust the brand palette, that's the only file to touch.
Every statistic, price, and claim in this codebase is either factual, clearly marked as indicative with a checked date, or a placeholder explicitly called out as such (see the header comment in /data/fare-observations.ts, the source of truth for every price shown, and this README). There is no "50,000+ travellers," no invented urgency, and no claim that can't be traced to a real, editable data file.
A wishlist of "critical additions" was evaluated against this codebase as part of a full ownership-level audit. Several were deliberately not built. This isn't an oversight — building them now would have been the wrong call, and it's worth recording the reasoning so it isn't re-litigated by accident later.
Built this pass: fare alerts now genuinely capture a nearest-airport preference (see components/sections/newsletter-section.tsx and app/api/subscribe/route.ts), route comparison sections on route pages where real alternative-airport data exists, and the visiting-family content architecture was kept as the core differentiator rather than diluted with new verticals.
Deliberately deferred — Hotels, Car Hire, Airport Lounges, Airport Parking. Each of these needs either a live inventory/booking integration or genuine local-market knowledge this codebase doesn't yet have. Shipping them now with placeholder content would repeat the exact mistake that kills early-stage travel sites: going wide and shallow instead of finishing the core network first. Revisit once the 16 core routes are live, indexed, and converting — not before.
Worth adding once traffic exists — eSIMs, Travel Insurance. Both are low-build-cost affiliate plays (a single resource page each, not a full vertical) and can be added quickly once there's an audience to serve them to. Don't build ahead of demand.
Travel Club (app/travel-club/page.tsx, components/sections/newsletter-section.tsx) deliberately does not claim automated live price tracking, because no such backend exists. What it promises instead is two manual things, both genuinely deliverable but requiring you to actually do them:
data/fare-observations.ts meaningfully better than what's been shown before, segment your email list by the TRAVEL_INTEREST and NEAREST_AIRPORT contact attributes (captured at signup) and send a short, honest email to the relevant segment only — not your whole list.data/routes.ts, do the same — email the segment tagged with that region.If you don't intend to do this manually on an ongoing basis, soften the Travel Club page's copy further rather than leaving a promise unfulfilled — an unfulfilled "we'll tell you" is worse for trust than not promising it at all.
Actual email templates for the welcome, first-value, and re-engagement emails — written to be sent manually,
with guidance on when to send each — live in docs/travel-club-email-sequence.md.
No double opt-in is currently configured. The POST /v3/contacts call in app/api/subscribe/route.ts adds a contact directly — it does not trigger a confirmation email by itself. If you want double opt-in (recommended for deliverability and GDPR consent records), set it up in Brevo directly: either build the sign-up form in Brevo's own form builder with "Double confirmation" enabled, or create a Brevo Automation workflow that watches for "contact added to list" and sends a confirmation email with a opt-in link. The application code cannot configure this from outside Brevo's dashboard — don't add UI copy implying a confirmation email exists until one actually does.
Every route added in this audit pass was checked against real airline schedule data before being written into data/routes.ts — not assumed from the brief. Two corrections worth flagging explicitly:
If you update data/routes.ts or data/airports.ts in future, verify new route claims the same way — a named UK city pair existing in a press release is not the same as a stable, bookable, ongoing service.
connectingAlternativeSome direct routes have a publicly announced service change — for example, IndiGo's Manchester–Delhi and Manchester–Mumbai services, both launched in 2025, with a change IndiGo has announced with effect from 31 August 2026. A plain isDirect: true/false boolean can't represent this honestly: the route is genuinely direct today, and an announcement is not proof of what happens on the date it names.
This is modelled in the append-only Route Status ledger, data/route-status-events.ts — never as
a field on the Route record itself. getRouteStatus(route, routeStatusEvents, nowIso)
(data/routes.ts) derives the route's current status from the ledger (verified-direct,
withdrawal-announced, service-ended, or verification-pending).
lib/route-status-copy.ts owns both getEffectiveRoutePresentation()—the canonical adapter every
public surface calls—and getRouteStatusCopy(), which produces validated, evidence-cited customer
copy for the route page and other surfaces.
Previously, a currently-direct route with an announced end date was modelled via Route.directServiceEndDate/directServiceEndNote fields, rendering a banner directly from route data. Those fields have been removed entirely: keeping them as a second, parallel mechanism alongside the ledger would recreate exactly the second-source-of-truth risk the ledger exists to prevent. Every route with a scheduled service change — direct or otherwise — is modelled in the ledger now.
Appending a new withdrawal/service-ended event: add a new, fully sourced RouteStatusEvent to data/route-status-events.ts (see that file's header comment for the exact invariants — an announcement is never proof of occurrence; only a freshly verified service-ended event with verifiedOccurrence: true may assert a direct service actually ended). Do not edit route.isDirect, route.airlineSlugs, route.frequency, or route.connectingAlternative as part of this — the ledger event alone drives the safe public presentation, and getRouteStatus()'s airline-scoped derivation depends on route.airlineSlugs staying as the historical direct-operator record for the event to resolve against.
connectingAlternative — populated for any route where a realistic 1-stop (or 2-stop) alternative matters — remains entirely separate, editorial content, never auto-populated or implied by a withdrawal/service-ended ledger event. "Direct service ended" and "a connecting journey exists" are two separate facts requiring separate evidence: publishing a current connecting-service claim requires its own independent verification, not a consequence of appending a ledger event.
data/travel-ready-rules.tsTravel Ready Check (JETSTASH_PRINCIPLES.md §14.3) answers "can I actually travel on these dates with the documents I have?" — passport validity and visa guidance across 8 countries (Pakistan, India, Bangladesh, Saudi Arabia, UAE, Qatar, Turkey, Morocco), British passport holders plus NICOP/POC, OCI and NVR document holders. Every fact lives in data/travel-ready-rules.ts, never scattered across components.
To add or refresh a rule:
gov.uk/foreign-travel-advice/<country>/entry-requirements) for passport validity, or the destination's own official visa portal for visa-requirement specifics. Never a blog, forum, or travel agency page, even if it looks current.requirement, minDaysValidityBeyondEntry, visaRequired, typicalProcessingDays (only if the official source states a firm figure — leave it unset otherwise) and caveat to match what you actually read.lastVerifiedDate to today and reviewDueDate 6 months out. Never bump these without re-checking the live source first — that's the exact stale-claim pattern §9 exists to prevent, applied to a compliance-adjacent feature where it matters more.TRAVEL_READY_SUPPORTED_COUNTRIES in lib/travel-ready-check.ts — the two must move together, or the destination selector and the decision tree disagree about coverage.description in app/travel-ready-check/page.tsx and the selector hint in components/travel-ready/travel-ready-check.tsx — plus this file's and CLAUDE.md's/JETSTASH_PRINCIPLES.md's country counts. These aren't derived from TRAVEL_READY_SUPPORTED_COUNTRIES (the metadata description has a hard 170-character budget that spelling out every country name can't always absorb), so tests/travel-ready-supported-countries.test.ts regression-tests that they stay in sync instead — a failing test there is the signal this step was missed, not a silent drift (this is exactly how Bangladesh was originally missed from both sentences after being added everywhere else).Once reviewDueDate passes, isRuleStale() makes the affected check degrade to "official confirmation required" automatically — check /founder's "Travel Ready Check — rules ops" section for what's coming due.
JetStash's differentiator versus a generic comparison site is accumulated, real history per route — not a bigger list of destinations. route-timeline.ts, route-warnings.ts, fare-observations.ts, booking-windows.ts, airport-notes.ts, traveller-tips.ts and community-notes.ts all follow the same rule:
FareObservation with a later observedDate — don't edit the old one. When a warning stops applying, flip its status to 'resolved' rather than deleting it.intro/bookingWindowNote, an airport's old practicalNotes) — none of it was invented to fill a gap. Keep it that way: a plausible-sounding fare, warning, or tip is exactly the kind of fabricated content this project's "No fabricated content" rule (above) exists to prevent.data/airlines.ts and data/peak-periods.ts exist because free-text airline names and peak-period labels had already drifted (e.g. 'UK summer holidays (Jul–Aug)' vs 'UK summer holidays') across routes.ts and destinations.ts. New routes should reference airlineSlugs/peakPeriodIds, adding a new canonical entry first if one genuinely doesn't exist yet — never re-typing a new label inline.data/community-notes.ts ships empty on purpose — there's no submission pipeline yet, and fabricating "traveller" testimonials would violate the same rule as a fabricated price. components/route/community-notes-panel.tsx renders a plain "not yet" state rather than pretending otherwise; follow that pattern for any other section that doesn't have real content yet.lib/freshness-thresholds.ts defines OBSERVATION_FRESH_DAYS/OBSERVATION_STALE_DAYS/SERVICE_END_WATCH_DAYS/RULE_REVIEW_WATCH_DAYS and the getFareFreshnessState() helper every fare-rendering component uses — change a threshold there, not in the component that happens to render it. A copy-paste FareObservation template lives at the top of data/fare-observations.ts for fast logging, and a weekly Vercel Cron (app/api/cron/fare-check-reminder/route.ts) emails a reminder for any priority route past due — see JETSTASH_PRINCIPLES.md §14.4 for why automated fare collection itself isn't currently safe.Recorded here so they aren't silently dropped, not because they're scheduled:
data/routes.ts — don't infer routes from demand alone.RegionGroups (they currently sit inside the broader mediterranean/north-africa/gulf groupings) once there's enough route depth in each to justify a dedicated hub, rather than adding hub pages ahead of the content that would fill them.lib/booking-providers.tsTRIPCOM_ROUTE_URLS in
lib/booking-providers.ts); the other 9 (all London-origin) have no booking CTA at all, by
design — Trip.com's own tools can't produce a Heathrow/Gatwick-specific dateless link, and no
generic fallback is used. Never infer a URL pattern: every entry is generated through the
dashboard, confirmed to land correctly, then added directly./app/api/route-watch/route.ts reuses BREVO_API_KEY/BREVO_LIST_ID and writes WATCH_AIRPORT, WATCH_DESTINATION, WATCH_ROUTE, WATCH_REGION, WATCH_INTENT. Note WATCH_ROUTE holds a comma-delimited list of up to 3 route slugs, not a single value — the shared cap is defined in lib/route-watch-config.ts (MAX_WATCHED_ROUTES) — deliberately reusing the existing attribute rather than adding a new one that would need re-provisioning. This is a storage mechanism only; it does not imply Travel Intelligence Engine integration./app/api/quote-request/route.ts and /app/api/contact/route.ts both reuse RESEND_API_KEY/CONTACT_TO_EMAIL (no new env vars) and now default to siteConfig.contactEmail in lib/site-config.ts — a real inbox, not a placeholder — when CONTACT_TO_EMAIL isn't set in Vercel. Every Umrah/family/group quote request lands in one inbox for manual follow-up; revisit whether that should become a rotation of partner agents or a shared inbox as volume grows.data/travel-ready-rules.ts's 19 rules across 8 countries were verified against live GOV.UK/official-portal pages in July 2026 with a 6-month review window — this is content, not code, and visa/passport rules change with little notice (Pakistan suspended visa-on-arrival for most nationalities on 1 January 2026 with no advance notice). See "Travel Ready Check — maintaining data/travel-ready-rules.ts" above for the re-verification procedure, and /founder's "Travel Ready Check — rules ops" section for what's currently due.