From fd6b5a0d68cbbef30b74364394d218736d12fe96 Mon Sep 17 00:00:00 2001 From: Mao Nakamoto <41178744+maonakamoto@users.noreply.github.com> Date: Fri, 28 Aug 2026 11:51:31 +0200 Subject: [PATCH] refactor(design): finish the colour sweep, and make it enforceable #144 put the bot pages on the brand. This does the other 60 files, so the whole product reads as one thing, and adds the guard that keeps it that way. ~410 raw palette classes became semantic tokens across the public marketing surface (/try, /enterprise, /about, /knowledge, /personal, /professionals, /bots, blog, navigation) and the app internals (dashboard, documents, chat, conversations, onboarding, auth, settings, profile). Gradient text is gone. Fifteen headings were `bg-gradient-to-r from-X to-Y bg-clip-text text-transparent`; they are now solid text-ink or text-action. globals.css says the identity is carried by typography, and a serif display does not need a gradient to do that. The 'B' mark was still a blue/purple gradient in BotNavigation's MOBILE menu. #144 only fixed the desktop header. Both now render the shared . Three defects introduced mid-sweep and corrected, all caught by reading the diff rather than trusting the regex: - degenerate gradients (`from-action-tint to-action-tint`) collapsed to solid - dead hovers (tint on tint) rebased to bg-surface + hover:bg-action-tint - `to-action\b` matched the prefix of `to-action-hover`, so a collapse left a stray `bg-action-hover` as a BASE background in 11 places -- those are the primary, not the hover shade Deliberately NOT changed: - components/knowledge/Callout.tsx -- its blue is the `info` half of an info/warning/error triad. Semantic status colour is a separate scale from the brand accent, and warning is amber, so ochre-ing info would make the two nearly indistinguishable. - the custom-bot surfaces -- custom_bots.accent_color is a real column with a CHECK constraint and the owner picks it. User data, not a design token. Never-twice: tests/__tests__/lib/design-tokens.test.ts refuses any raw brand-palette class, and any bg-clip-text, outside those two documented exceptions. Mutation-verified -- swap one token back to bg-blue-600 and it fails and names the file. No allowlist debt: every other file was swept rather than excused. verify: format, lint, typecheck, 256 tests, build -- all green. Twelve pages smoke-tested and checked visually against a local production build. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HVwg8DKHQktxJuHeLM3xpG --- app/about/page.tsx | 22 +++-- app/auth/callback/page.tsx | 4 +- app/auth/forgot-password/page.tsx | 10 +-- app/auth/reset-password/page.tsx | 14 +-- app/auth/signin/page.tsx | 12 +-- app/auth/signup/page.tsx | 14 +-- app/auth/verify-email/page.tsx | 10 +-- app/bots/BotNavigation.tsx | 6 +- app/bots/page.tsx | 14 +-- app/create/page.tsx | 24 ++--- app/dashboard/page.tsx | 2 +- app/demo/page.tsx | 14 +-- app/documents/page.tsx | 14 +-- app/enterprise/page.tsx | 36 +++----- app/global-error.tsx | 4 +- app/infrastructure/page.tsx | 4 +- app/knowledge/guides/[slug]/page.tsx | 30 +++---- app/knowledge/guides/page.tsx | 12 +-- app/knowledge/infrastructure/page.tsx | 46 +++++----- app/knowledge/page.tsx | 35 +++----- app/my-data/page.tsx | 12 +-- app/personal/page.tsx | 30 +++---- app/professionals/[slug]/page.tsx | 12 +-- app/professionals/page.tsx | 14 ++- app/profile/page.tsx | 9 +- app/settings/page.tsx | 12 +-- app/try/page.tsx | 19 ++-- components/ErrorBoundary.tsx | 2 +- components/Navigation.tsx | 2 +- components/blog/MDXComponents.tsx | 2 +- components/blog/ServerMDXContent.tsx | 2 +- components/chat/ChatAuthCTA.tsx | 10 +-- components/chat/ModelIndicator.tsx | 2 +- components/chat/ModelSelector.tsx | 4 +- components/chat/PrivacyTierInfo.tsx | 4 +- components/conversations/ConversationItem.tsx | 2 +- components/conversations/ConversationList.tsx | 4 +- components/dashboard/DashboardEmptyState.tsx | 16 ++-- components/dashboard/QuickActions.tsx | 12 +-- components/dashboard/RecentActivity.tsx | 11 +-- components/dashboard/StatsGrid.tsx | 6 +- components/documents/AddToBotModal.tsx | 11 +-- components/documents/CategorySelector.tsx | 2 +- components/documents/ChatMessageList.tsx | 2 +- components/documents/DocumentChatPanel.tsx | 6 +- components/documents/DocumentListPanel.tsx | 8 +- components/documents/DocumentUploadButton.tsx | 2 +- components/infrastructure/StorageCard.tsx | 4 +- components/knowledge/GuideCard.tsx | 8 +- components/knowledge/TableOfContents.tsx | 2 +- components/navigation/AuthNav.tsx | 4 +- components/navigation/MegaMenuItem.tsx | 4 +- components/navigation/MegaMenuPanel.tsx | 4 +- components/navigation/NavItem.tsx | 8 +- components/navigation/SiteMenuDropdown.tsx | 6 +- components/onboarding/OnboardingChecklist.tsx | 8 +- components/onboarding/OnboardingStep.tsx | 6 +- components/shared/BotFeatureCard.tsx | 6 +- components/shared/BotNotFoundFallback.tsx | 2 +- components/shared/LoadingSpinner.tsx | 2 +- components/shared/ProfessionalDemo.tsx | 4 +- components/shared/UserAvatar.tsx | 2 +- components/shared/demo/DemoFileUpload.tsx | 2 +- .../shared/quick-create/TemplatePicker.tsx | 4 +- components/try/TryChatPanel.tsx | 22 ++--- components/try/TryDocumentsPanel.tsx | 6 +- tests/__tests__/lib/design-tokens.test.ts | 90 +++++++++++++++++++ 67 files changed, 387 insertions(+), 352 deletions(-) create mode 100644 tests/__tests__/lib/design-tokens.test.ts diff --git a/app/about/page.tsx b/app/about/page.tsx index b9629e20..5e458383 100644 --- a/app/about/page.tsx +++ b/app/about/page.tsx @@ -4,13 +4,11 @@ import { professionals } from '@/data/professionals'; export default function About() { return ( -
+
{/* Hero */}

- - Expert Advice for Everyone - + Expert Advice for Everyone

Botsmann brings AI-powered professionals to your fingertips. Get guidance from specialized @@ -66,7 +64,7 @@ export default function About() { {/* Privacy First */}

Privacy First

-
+

Your conversations with our AI professionals are private. We do not use your data to train models. We do not share your information with third parties. Your health @@ -100,7 +98,7 @@ export default function About() { {/* The Future: Embodied AI */}

The Future: Embodied AI

-
+

We envision a future where AI professionals are not just on your screen but present in your world. Imagine a health advisor that monitors your wellbeing through wearables. A @@ -130,25 +128,25 @@ export default function About() {

  • - + On-premises deployment options
  • - + SSO and team management
  • - + HIPAA and GDPR compliance
  • - + Custom training on your knowledge base
Learn About Enterprise @@ -165,7 +163,7 @@ export default function About() { {/* CTA */}
-
+

Ready to Meet Your AI Professional?

diff --git a/app/auth/callback/page.tsx b/app/auth/callback/page.tsx index 7505e934..606a8e3e 100644 --- a/app/auth/callback/page.tsx +++ b/app/auth/callback/page.tsx @@ -154,7 +154,7 @@ function AuthCallbackContent() {
Go to Sign In @@ -200,7 +200,7 @@ function LoadingFallback() { */ export default function AuthCallbackPage() { return ( -
+
diff --git a/app/auth/forgot-password/page.tsx b/app/auth/forgot-password/page.tsx index f64c2add..55c33fca 100644 --- a/app/auth/forgot-password/page.tsx +++ b/app/auth/forgot-password/page.tsx @@ -85,7 +85,7 @@ export default function ForgotPasswordPage() { const isRateLimited = rateLimitSeconds > 0; return ( -
+
@@ -106,7 +106,7 @@ export default function ForgotPasswordPage() {

Didn't receive the email? Check your spam folder or try again.

- + Back to Sign In
@@ -154,7 +154,7 @@ export default function ForgotPasswordPage() { onChange={(e) => setEmail(e.target.value)} required disabled={isRateLimited} - className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-50 disabled:text-gray-500" + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-action disabled:bg-gray-50 disabled:text-gray-500" placeholder="you@example.com" />
@@ -162,7 +162,7 @@ export default function ForgotPasswordPage() { diff --git a/app/auth/signin/page.tsx b/app/auth/signin/page.tsx index 36cc46ea..62297220 100644 --- a/app/auth/signin/page.tsx +++ b/app/auth/signin/page.tsx @@ -85,7 +85,7 @@ export default function SignInPage() { const isRateLimited = rateLimitSeconds > 0; return ( -
+
@@ -122,7 +122,7 @@ export default function SignInPage() { onChange={(e) => setEmail(e.target.value)} required disabled={isRateLimited} - className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-50 disabled:text-gray-500" + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-action disabled:bg-gray-50 disabled:text-gray-500" placeholder="you@example.com" />
@@ -134,7 +134,7 @@ export default function SignInPage() { Forgot password? @@ -147,7 +147,7 @@ export default function SignInPage() { required disabled={isRateLimited} minLength={6} - className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-50 disabled:text-gray-500" + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-action disabled:bg-gray-50 disabled:text-gray-500" placeholder="Enter your password" />
@@ -155,7 +155,7 @@ export default function SignInPage() { @@ -163,7 +163,7 @@ export default function SignInPage() {
Don't have an account?{' '} - + Sign up
diff --git a/app/auth/signup/page.tsx b/app/auth/signup/page.tsx index 078f1d7e..16a44369 100644 --- a/app/auth/signup/page.tsx +++ b/app/auth/signup/page.tsx @@ -87,7 +87,7 @@ export default function SignUpPage() { const isRateLimited = rateLimitSeconds > 0; return ( -
+
@@ -105,7 +105,7 @@ export default function SignUpPage() { We sent a confirmation link to {email}. Click the link to activate your account.

- + Back to Sign In
@@ -149,7 +149,7 @@ export default function SignUpPage() { onChange={(e) => setEmail(e.target.value)} required disabled={isRateLimited} - className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-50 disabled:text-gray-500" + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-action disabled:bg-gray-50 disabled:text-gray-500" placeholder="you@example.com" />
@@ -166,7 +166,7 @@ export default function SignUpPage() { required disabled={isRateLimited} minLength={PASSWORD_MIN_LENGTH} - className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-50 disabled:text-gray-500" + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-action disabled:bg-gray-50 disabled:text-gray-500" placeholder={`At least ${PASSWORD_MIN_LENGTH} characters`} />
@@ -186,7 +186,7 @@ export default function SignUpPage() { required disabled={isRateLimited} minLength={PASSWORD_MIN_LENGTH} - className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-50 disabled:text-gray-500" + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-action disabled:bg-gray-50 disabled:text-gray-500" placeholder="Confirm your password" />
@@ -194,7 +194,7 @@ export default function SignUpPage() { @@ -266,7 +266,7 @@ const DemoPage: FC = () => { Want this for your business? This is a simplified demo. For production, we use vector embeddings for semantic search, and can deploy locally on your infrastructure for maximum privacy.{' '} - + Book a consultation →

diff --git a/app/documents/page.tsx b/app/documents/page.tsx index 9d224b27..9286ce35 100644 --- a/app/documents/page.tsx +++ b/app/documents/page.tsx @@ -67,7 +67,7 @@ export default function DocumentsPage() { if (!user) { return ( -
+

Sign in required

@@ -76,7 +76,7 @@ export default function DocumentsPage() {

Sign In @@ -98,7 +98,7 @@ export default function DocumentsPage() { const hasReadyDocuments = documents.some((d) => d.status === DOCUMENT_STATUS.READY); return ( -
+
{/* Page Header with User Controls */}
@@ -144,16 +144,16 @@ export default function DocumentsPage() {
{/* Info Box */} -
-

How it works

-
    +
    +

    How it works

    +
    1. Upload a document (PDF, TXT, or Markdown)
    2. Click "Process" to extract and index the content
    3. Ask questions in the chat - the AI will search your documents and provide answers
    -

    +

    Your documents are private and only accessible to you. Processing happens locally using free AI models. Your conversations are saved automatically.

    diff --git a/app/enterprise/page.tsx b/app/enterprise/page.tsx index 8e666552..3e73cd35 100644 --- a/app/enterprise/page.tsx +++ b/app/enterprise/page.tsx @@ -146,29 +146,25 @@ export default function EnterprisePage() { ]; return ( -
    +
    {/* Background Effects */}
    -
    -
    +
    +
    {/* Hero */}
    -
    - +
    + Enterprise-Grade AI Professionals

    - - AI Professionals for - + AI Professionals for
    - - Your Organization - + Your Organization

    @@ -179,7 +175,7 @@ export default function EnterprisePage() {

    Schedule a Demo @@ -200,7 +196,7 @@ export default function EnterprisePage() { Try Our Professionals @@ -226,9 +222,7 @@ export default function EnterprisePage() {

    - - Built for Your Industry - + Built for Your Industry

    AI professionals tailored to the specific needs of your organization @@ -263,9 +257,7 @@ export default function EnterprisePage() {

    - - Enterprise Features - + Enterprise Features

    Everything you need to deploy AI professionals at scale @@ -278,7 +270,7 @@ export default function EnterprisePage() { key={feature.title} className="bg-white rounded-xl p-6 shadow-md border border-gray-100" > -

    +
    {feature.icon}

    {feature.title}

    @@ -320,9 +312,7 @@ export default function EnterprisePage() {

    - - Get in Touch - + Get in Touch

    Schedule a demo or discuss your specific requirements with our team diff --git a/app/global-error.tsx b/app/global-error.tsx index f97b5764..ec81ae5e 100644 --- a/app/global-error.tsx +++ b/app/global-error.tsx @@ -56,7 +56,7 @@ export default function GlobalError({

    @@ -70,7 +70,7 @@ export default function GlobalError({

    If this problem persists, please{' '} - + contact support

    diff --git a/app/infrastructure/page.tsx b/app/infrastructure/page.tsx index ad849fdf..d04fe7a0 100644 --- a/app/infrastructure/page.tsx +++ b/app/infrastructure/page.tsx @@ -177,7 +177,7 @@ export default function InfrastructurePage() { const selectedProviderData = getProviderById(selectedProvider); return ( -
    +
    {/* Page Header */}
    @@ -308,7 +308,7 @@ export default function InfrastructurePage() { diff --git a/app/knowledge/guides/[slug]/page.tsx b/app/knowledge/guides/[slug]/page.tsx index 2a71eace..83fdc36c 100644 --- a/app/knowledge/guides/[slug]/page.tsx +++ b/app/knowledge/guides/[slug]/page.tsx @@ -73,7 +73,7 @@ const mdxComponents = { : ''; return (

    - + {children}

    @@ -89,7 +89,7 @@ const mdxComponents = { : ''; return (

    - + {children}

    @@ -118,7 +118,7 @@ const mdxComponents = { a: ({ href, children, ...props }: React.AnchorHTMLAttributes) => ( ) => (
    {children} @@ -217,21 +217,21 @@ export default async function GuidePage({ params }: GuidePageProps) { return (
    {/* Hero Section */} -
    +
    {/* Breadcrumb */}