From c00400b355186cf945b799baae6c6c01e5eb2066 Mon Sep 17 00:00:00 2001 From: Gaurav Jadhav Date: Thu, 10 Sep 2026 10:45:32 +0530 Subject: [PATCH 1/5] fix(web): rebuild the thank-you page on the site's own design language The page shipped as a bare left-aligned Section on the default background, while every form that leads to it opens on the dark violet band. Arriving there read as a drop onto an unstyled page rather than the last step of one flow. Rebuilt on the canonical band with the faded grid asset, and lifted the panel out of its lower edge, which is the idiom the Book a Demo form already uses. Three real defects went with it: - The primary CTA pointed at /clean-images, which 404s. The route is /cleanstart-images. The deal-registration secondary pointed at /compare, also a 404, and contact pointed at /knowledge-hub, which redirects into a single article rather than a listing. - The secondary CTA was invisible. It used .cs-link-cta, which is white with a #33BAEC hover because it is built for the dark bands; on the white card that is white on white, and the hover measures 2.2:1. Added a light-surface variant on #3960F9, the blue already used for focus rings. - The copy promised a response 'usually within one business day', reintroducing exactly what 9f0defc2 removed from every form and confirmation email a day earlier, on the grounds that it reads as a commitment the team has not agreed to. Also dropped the defining-by-negation tic the copy had picked up in four places ('not an autoresponder sequence', 'rather than a shared inbox nobody owns'), and raised the band's top padding floor so the eyebrow pill clears the overlaid header on narrow viewports. --- apps/web/src/app/globals.css | 31 +++ .../sections/thank-you/ThankYouContent.tsx | 204 +++++++++++++----- apps/web/src/lib/thank-you/content.ts | 24 +-- 3 files changed, 188 insertions(+), 71 deletions(-) diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 8ee1a16b..8472e705 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -6335,3 +6335,34 @@ body { outline: 2px solid #3960f9; outline-offset: 2px; } + +/* Secondary CTA on a light surface (thank-you card). + `.cs-link-cta` cannot be reused here: it is white with a #33BAEC hover, both + built for the dark bands, so on white it renders invisible and its hover + fails contrast. #3960F9 measures 4.8:1 on white. */ +.cs-thank-you-secondary { + display: inline-flex; + align-items: center; + gap: 6px; + font-family: var(--font-sora), ui-sans-serif, system-ui, sans-serif; + font-weight: 600; + letter-spacing: -0.01em; + color: #3960F9; + text-decoration: none; + padding: 4px 0; + transition: color 200ms ease; +} + +.cs-thank-you-secondary:hover { + color: #2748D6; + text-decoration: underline; + text-underline-offset: 3px; +} + +.cs-thank-you-secondary svg { + transition: transform 200ms ease; +} + +.cs-thank-you-secondary:hover svg { + transform: translateX(3px); +} diff --git a/apps/web/src/components/sections/thank-you/ThankYouContent.tsx b/apps/web/src/components/sections/thank-you/ThankYouContent.tsx index 684d23cb..1fffd253 100644 --- a/apps/web/src/components/sections/thank-you/ThankYouContent.tsx +++ b/apps/web/src/components/sections/thank-you/ThankYouContent.tsx @@ -1,6 +1,7 @@ import Link from "next/link"; -import { Container, Section } from "@/components/layout"; +import { Container } from "@/components/layout"; +import { HeroReveal } from "@/components/ui/Reveal"; import type { ThankYouContent as Content } from "@/lib/thank-you/content"; /** @@ -10,77 +11,147 @@ import type { ThankYouContent as Content } from "@/lib/thank-you/content"; * converted is at peak intent, and the inline banner it replaces vanished after * five seconds. So it leads with confirmation, says plainly what happens next, * then offers the two next steps most likely to matter. + * + * Built on the same dark band the originating form pages open with, so arriving + * here reads as the next step in one flow rather than a drop onto a bare page. + * The card overlaps the band's lower edge, which is the site's existing idiom + * for lifting a panel out of a gradient (see the Book a Demo form). */ export function ThankYouContent({ content }: { content: Content }): React.ReactElement { return ( -
+
+
+ {/* Bleeds past the 1440 viewport on each side, matching DemoHero. */} +
+ + {/* The asset carries its own edge fade; a CSS-tiled grid would run at + uniform opacity to the section edge and read as graph paper. */} + {/* eslint-disable-next-line @next/next/no-img-element */} + + +
+ + + + + + {content.eyebrow} + + + {/* tabIndex so the tracker can move focus here after a soft + navigation, which otherwise announces nothing. */} +

+ {content.headline} +

+ +

+ {content.body} +

+
+
+
+ + {/* Lifted out of the band's lower edge. */} -
- +

- - - - {content.eyebrow} - - - {/* tabIndex so the tracker can move focus here after a soft - navigation, which otherwise announces nothing. */} -

- {content.headline} -

- + What happens next +

- {content.body} + {content.whatHappensNext}

-

- WHAT HAPPENS NEXT -

-

- {content.whatHappensNext} -

-
- -
{content.primary.label} + {/* Not `.cs-link-cta`: that class is white with a #33BAEC hover, + built for the dark bands. On this white card it renders white on + white, and its hover colour measures 2.2:1 here. #3960F9 is the + accessible blue already used for focus rings. */} {content.secondary.label} + + +
-
+ +
+
); } diff --git a/apps/web/src/lib/thank-you/content.ts b/apps/web/src/lib/thank-you/content.ts index 9db537e3..d8b2d24c 100644 --- a/apps/web/src/lib/thank-you/content.ts +++ b/apps/web/src/lib/thank-you/content.ts @@ -36,22 +36,22 @@ export const THANK_YOU_CONTENT = { "book-a-demo": { eyebrow: "Demo requested", headline: "Your demo request is in", - body: "A solutions engineer will read what you sent and come back to you directly, usually within one business day.", + body: "A solutions engineer is reading what you sent and will come back to you directly.", whatHappensNext: - "You will get an email from a real person, not an autoresponder sequence. If your timeline is tight, say so in the reply and we will work to it.", - primary: { label: "See how images are hardened", href: "/clean-images" }, - secondary: { label: "Read the SBOM guide", href: "/resource-center" }, + "You will hear from a named person who has read your message, not a sequence. If your timeline is tight, say so in your reply and we will plan around it.", + primary: { label: "See how our images are built", href: "/cleanstart-images" }, + secondary: { label: "Browse the resource center", href: "/resource-center" }, metaTitle: "Demo requested", metaDescription: "Your CleanStart demo request has been received.", }, contact: { eyebrow: "Message sent", headline: "Thanks, we have your message", - body: "It has gone to the team who can actually answer it, rather than a shared inbox nobody owns.", + body: "It has gone to the team who can answer it.", whatHappensNext: - "Expect a reply from a named person. If it turns out someone else is better placed to help, we will introduce you rather than forward you.", - primary: { label: "Browse the knowledge hub", href: "/knowledge-hub" }, - secondary: { label: "See open roles", href: "/careers" }, + "Someone from that team will reply to you directly. If a colleague is better placed to help, we will introduce you.", + primary: { label: "Browse the resource center", href: "/resource-center" }, + secondary: { label: "Read the blog", href: "/blogs" }, metaTitle: "Message sent", metaDescription: "Your message to CleanStart has been received.", }, @@ -60,18 +60,18 @@ export const THANK_YOU_CONTENT = { headline: "Your deal registration is logged", body: "The partnerships team has it, along with the prospect details you entered.", whatHappensNext: - "We will confirm registration and come back on deal protection within one business day. Nothing is contacted on your prospect's side until you tell us to.", + "We will confirm the registration and come back to you on deal protection. Your prospect is not contacted until you tell us to.", primary: { label: "Partner resources", href: "/partners" }, - secondary: { label: "Compare against alternatives", href: "/compare" }, + secondary: { label: "Browse the resource center", href: "/resource-center" }, metaTitle: "Deal registered", metaDescription: "Your CleanStart deal registration has been received.", }, "job-application": { eyebrow: "Application received", headline: "Thanks for applying", - body: "Your application and CV are with the hiring team. Every one is read by a person.", + body: "Your application and CV are with the hiring team, and every one is read by a person.", whatHappensNext: - "If there is a fit you will hear from us with next steps. If there is not, you will still hear back rather than being left wondering.", + "You will hear back either way, whether or not there is a fit. If there is, the next step is a conversation with someone on the team you would join.", primary: { label: "See all open roles", href: "/careers" }, secondary: { label: "How we work", href: "/teams" }, metaTitle: "Application received", From 380129da68929e540b2acbff83788e5adc5665db Mon Sep 17 00:00:00 2001 From: Gaurav Jadhav Date: Thu, 10 Sep 2026 11:03:02 +0530 Subject: [PATCH 2/5] fix(web): drop the success pill from the thank-you page The page opened with a green tick pill reading 'Demo requested'. Nothing else on the site renders a success badge, and the eyebrow it was built from is a metadata convention rather than a visual one: pages pass `eyebrow` to buildPageMetadata so og.ts can print a category onto the share card. On the page it read as a generic form-submitted toast, and it restated what the headline underneath it already says. Removed the pill and the now-dead `eyebrow` field, which duplicated `metaTitle` verbatim in all four entries. The headline leads instead. --- .../sections/thank-you/ThankYouContent.tsx | 32 ++++--------------- apps/web/src/lib/thank-you/content.ts | 5 --- 2 files changed, 6 insertions(+), 31 deletions(-) diff --git a/apps/web/src/components/sections/thank-you/ThankYouContent.tsx b/apps/web/src/components/sections/thank-you/ThankYouContent.tsx index 1fffd253..8384b76d 100644 --- a/apps/web/src/components/sections/thank-you/ThankYouContent.tsx +++ b/apps/web/src/components/sections/thank-you/ThankYouContent.tsx @@ -62,37 +62,17 @@ export function ThankYouContent({ content }: { content: Content }): React.ReactE }} > - - - - - {content.eyebrow} - - + {/* No status pill above this heading. Nothing on the site renders a + success badge, and `eyebrow` here is a metadata convention, not a + visual one: pages pass it to buildPageMetadata so og.ts can print + a category onto the share card. A pill on the page read as a + generic form-submitted toast. The headline states the outcome. */} {/* tabIndex so the tracker can move focus here after a soft navigation, which otherwise announces nothing. */}

Date: Thu, 10 Sep 2026 11:16:29 +0530 Subject: [PATCH 3/5] fix(web): make the thank-you page say what actually happens next The 'what happens next' block was reassurance rather than information. It told the visitor they would hear from 'a named person who has read your message, not a sequence', which is corporate-speak wrapped around the same defining-by-negation tic the previous pass was supposed to remove, and it closed with 'say so in your reply' when the visitor has just submitted a form and has no thread to reply to. Rewritten from the confirmation emails these forms actually send, so the page and the inbox agree. Each block now names the subject line the visitor is about to receive and what to do with it. The careers case is the reason this had to be per-form rather than generic: that confirmation is sent from an unmonitored address and tells the applicant not to reply, so pointing them at a reply would have contradicted it. It points at the contact form instead. --- apps/web/src/lib/thank-you/content.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/web/src/lib/thank-you/content.ts b/apps/web/src/lib/thank-you/content.ts index 545bbb15..44c4ed6f 100644 --- a/apps/web/src/lib/thank-you/content.ts +++ b/apps/web/src/lib/thank-you/content.ts @@ -34,9 +34,9 @@ export interface ThankYouContent { export const THANK_YOU_CONTENT = { "book-a-demo": { headline: "Your demo request is in", - body: "A solutions engineer is reading what you sent and will come back to you directly.", + body: "One of our solutions specialists will read what you sent and reach out to arrange a time.", whatHappensNext: - "You will hear from a named person who has read your message, not a sequence. If your timeline is tight, say so in your reply and we will plan around it.", + "A confirmation is on its way to your inbox, subject 'Your CleanStart demo request'. Reply to it with what you want the demo to cover: your current base images, the CVE burden you are carrying, or the audit you are preparing for.", primary: { label: "See how our images are built", href: "/cleanstart-images" }, secondary: { label: "Browse the resource center", href: "/resource-center" }, metaTitle: "Demo requested", @@ -46,7 +46,7 @@ export const THANK_YOU_CONTENT = { headline: "Thanks, we have your message", body: "It has gone to the team who can answer it.", whatHappensNext: - "Someone from that team will reply to you directly. If a colleague is better placed to help, we will introduce you.", + "A confirmation is on its way to your inbox, subject 'We have received your message'. If what you sent is urgent, reply to it and your message reaches the team directly.", primary: { label: "Browse the resource center", href: "/resource-center" }, secondary: { label: "Read the blog", href: "/blogs" }, metaTitle: "Message sent", @@ -56,7 +56,7 @@ export const THANK_YOU_CONTENT = { headline: "Your deal registration is logged", body: "The partnerships team has it, along with the prospect details you entered.", whatHappensNext: - "We will confirm the registration and come back to you on deal protection. Your prospect is not contacted until you tell us to.", + "A confirmation is on its way to your inbox, subject 'We have your deal registration'. The partner team reviews the registration and confirms next steps with you. Your prospect is not contacted until you say so.", primary: { label: "Partner resources", href: "/partners" }, secondary: { label: "Browse the resource center", href: "/resource-center" }, metaTitle: "Deal registered", @@ -64,9 +64,9 @@ export const THANK_YOU_CONTENT = { }, "job-application": { headline: "Thanks for applying", - body: "Your application and CV are with the hiring team, and every one is read by a person.", + body: "Your application and CV are with the hiring team.", whatHappensNext: - "You will hear back either way, whether or not there is a fit. If there is, the next step is a conversation with someone on the team you would join.", + "Every application is read, and if your experience lines up with the role we will be in touch to arrange a first conversation. The confirmation in your inbox comes from an unmonitored address, so use the contact form if you need to reach us.", primary: { label: "See all open roles", href: "/careers" }, secondary: { label: "How we work", href: "/teams" }, metaTitle: "Application received", From 904a0f7d466ec49cd0b73fae1fbf96d79de7a236 Mon Sep 17 00:00:00 2001 From: Gaurav Jadhav Date: Thu, 10 Sep 2026 11:29:50 +0530 Subject: [PATCH 4/5] fix(web): cut the overstated claims from the thank-you copy Three things the copy asserted that it could not back. It quoted the confirmation email's subject line so the visitor could find it. That duplicated a string owned by apps/cms into apps/web, and it had already drifted: the page said 'We have received your message' where the email sends "We've received your message". One place to keep true is now zero, since the page just says to check the inbox. Deal registration claimed 'your prospect is not contacted until you say so'. Nothing in the deal-registration flow or its email says that. It is a commitment about how the partner process works, invented on a public page. The careers page sent applicants to the contact form when they could not reply to the unmonitored confirmation. Nothing routes candidate questions there, and that form goes to a sales inbox. Also trimmed the demo body to what its email actually says. Every remaining sentence now traces to a line in the email the same form sends. --- apps/web/src/lib/thank-you/content.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/web/src/lib/thank-you/content.ts b/apps/web/src/lib/thank-you/content.ts index 44c4ed6f..6f1095b4 100644 --- a/apps/web/src/lib/thank-you/content.ts +++ b/apps/web/src/lib/thank-you/content.ts @@ -34,9 +34,9 @@ export interface ThankYouContent { export const THANK_YOU_CONTENT = { "book-a-demo": { headline: "Your demo request is in", - body: "One of our solutions specialists will read what you sent and reach out to arrange a time.", + body: "One of our solutions specialists will reach out to arrange a time.", whatHappensNext: - "A confirmation is on its way to your inbox, subject 'Your CleanStart demo request'. Reply to it with what you want the demo to cover: your current base images, the CVE burden you are carrying, or the audit you are preparing for.", + "Check your inbox for a confirmation. Reply to it with anything you want the demo to cover: your base images, the CVEs you are dealing with, or an audit you are preparing for.", primary: { label: "See how our images are built", href: "/cleanstart-images" }, secondary: { label: "Browse the resource center", href: "/resource-center" }, metaTitle: "Demo requested", @@ -46,7 +46,7 @@ export const THANK_YOU_CONTENT = { headline: "Thanks, we have your message", body: "It has gone to the team who can answer it.", whatHappensNext: - "A confirmation is on its way to your inbox, subject 'We have received your message'. If what you sent is urgent, reply to it and your message reaches the team directly.", + "Check your inbox for a confirmation. If your message is urgent, reply to it and it reaches the team directly.", primary: { label: "Browse the resource center", href: "/resource-center" }, secondary: { label: "Read the blog", href: "/blogs" }, metaTitle: "Message sent", @@ -56,7 +56,7 @@ export const THANK_YOU_CONTENT = { headline: "Your deal registration is logged", body: "The partnerships team has it, along with the prospect details you entered.", whatHappensNext: - "A confirmation is on its way to your inbox, subject 'We have your deal registration'. The partner team reviews the registration and confirms next steps with you. Your prospect is not contacted until you say so.", + "Check your inbox for a confirmation. The partner team reviews the registration and confirms next steps with you.", primary: { label: "Partner resources", href: "/partners" }, secondary: { label: "Browse the resource center", href: "/resource-center" }, metaTitle: "Deal registered", @@ -66,7 +66,7 @@ export const THANK_YOU_CONTENT = { headline: "Thanks for applying", body: "Your application and CV are with the hiring team.", whatHappensNext: - "Every application is read, and if your experience lines up with the role we will be in touch to arrange a first conversation. The confirmation in your inbox comes from an unmonitored address, so use the contact form if you need to reach us.", + "Every application is reviewed. If your experience lines up with what the role needs, we will be in touch to arrange a first conversation.", primary: { label: "See all open roles", href: "/careers" }, secondary: { label: "How we work", href: "/teams" }, metaTitle: "Application received", From 2314d1a61bdbf80c23f20f45e323c0333f45c19f Mon Sep 17 00:00:00 2001 From: Gaurav Jadhav Date: Thu, 10 Sep 2026 11:31:24 +0530 Subject: [PATCH 5/5] fix(cms): register the five job task slugs the enum never learned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five tasks were added to payload.config.ts without a matching migration, so `enum_payload_jobs_task_slug` never learned them. Every attempt to queue one failed the INSERT into payload_jobs with invalid input value for enum enum_payload_jobs_task_slug: "refreshCrux" Payload's scheduleQueueable catches that and marks the task `errored`, while defaultAfterSchedule still advances lastScheduledRun in the job-stats global. So the scheduler reported healthy timestamps for tasks that had never run once, and analyticsCache simply stopped updating for those keys with nothing anywhere to explain it. Affected, none of which has ever executed in production: purgeConsentLog daily 04:00 GDPR consent-log 24-month retention purgeDealRegistrations daily 03:30 GDPR deal-registration PII 365-day purge retryDealSync every 10m deal-registration HubSpot sync retry refreshContentInsights daily 06:30 content-insights snapshot rebuild refreshCrux daily 06:45 Core Web Vitals origin cache Two are retention jobs, so this is a compliance gap rather than only a stale dashboard. The task handlers themselves were never broken: refreshCrux was verified end to end against production — credentials resolve, the CrUX API returns both form factors, and the analyticsCache write succeeds. ADD VALUE IF NOT EXISTS keeps the migration idempotent and lets it no-op on a database built from a later baseline. Postgres cannot drop an enum value, so `down` is deliberately a no-op. --- ...60910_060000_add_missing_job_task_slugs.ts | 34 +++++++++++++++++++ apps/cms/src/migrations/index.ts | 6 ++++ 2 files changed, 40 insertions(+) create mode 100644 apps/cms/src/migrations/20260910_060000_add_missing_job_task_slugs.ts diff --git a/apps/cms/src/migrations/20260910_060000_add_missing_job_task_slugs.ts b/apps/cms/src/migrations/20260910_060000_add_missing_job_task_slugs.ts new file mode 100644 index 00000000..44bca25d --- /dev/null +++ b/apps/cms/src/migrations/20260910_060000_add_missing_job_task_slugs.ts @@ -0,0 +1,34 @@ +import { type MigrateUpArgs, sql } from '@payloadcms/db-postgres' + +/** + * Adds the five task slugs that were registered in `payload.config.ts` without + * a matching migration, so `enum_payload_jobs_task_slug` never learned them. + * + * Every attempt to queue one failed the INSERT into `payload_jobs` with + * `invalid input value for enum enum_payload_jobs_task_slug`. Payload's + * `scheduleQueueable` catches that and reports the task as `errored`, and + * `defaultAfterSchedule` still advances `lastScheduledRun` in the job-stats + * global — so the scheduler looked healthy while these five had never run once. + * + * Two of them are GDPR retention jobs (consent-log 24-month, deal-registration + * PII 365-day), so this is a compliance fix, not only a dashboard one. + * + * `ADD VALUE` cannot run inside a transaction on Postgres < 12; on 16 it can, + * provided the new label is not used in the same transaction. This migration + * only adds labels, so it is safe. `IF NOT EXISTS` keeps it idempotent and + * lets it no-op on a database built from a later baseline. + */ +export async function up({ db }: MigrateUpArgs): Promise { + await db.execute(sql` + ALTER TYPE "public"."enum_payload_jobs_task_slug" ADD VALUE IF NOT EXISTS 'purgeConsentLog'; + ALTER TYPE "public"."enum_payload_jobs_task_slug" ADD VALUE IF NOT EXISTS 'retryDealSync'; + ALTER TYPE "public"."enum_payload_jobs_task_slug" ADD VALUE IF NOT EXISTS 'purgeDealRegistrations'; + ALTER TYPE "public"."enum_payload_jobs_task_slug" ADD VALUE IF NOT EXISTS 'refreshContentInsights'; + ALTER TYPE "public"."enum_payload_jobs_task_slug" ADD VALUE IF NOT EXISTS 'refreshCrux';`) +} + +export async function down(): Promise { + // Postgres cannot remove a value from an enum type. The five labels are left + // in place; unused labels are harmless, and dropping the tasks is a + // code-only change. +} diff --git a/apps/cms/src/migrations/index.ts b/apps/cms/src/migrations/index.ts index 30bae10d..28ace761 100644 --- a/apps/cms/src/migrations/index.ts +++ b/apps/cms/src/migrations/index.ts @@ -57,6 +57,7 @@ import * as migration_20260731_120000_add_legal_role from './20260731_120000_add import * as migration_20260821_120000_faq_answer_richtext from './20260821_120000_faq_answer_richtext'; import * as migration_20260909_120000_add_form_tel_and_business_email from './20260909_120000_add_form_tel_and_business_email'; import * as migration_20260909_180000_add_submission_attribution from './20260909_180000_add_submission_attribution'; +import * as migration_20260910_060000_add_missing_job_task_slugs from './20260910_060000_add_missing_job_task_slugs'; export const migrations = [ { @@ -329,4 +330,9 @@ export const migrations = [ down: migration_20260909_180000_add_submission_attribution.down, name: '20260909_180000_add_submission_attribution', }, + { + up: migration_20260910_060000_add_missing_job_task_slugs.up, + down: migration_20260910_060000_add_missing_job_task_slugs.down, + name: '20260910_060000_add_missing_job_task_slugs', + }, ];