Files
novalon-website/CLAUDE.md
T
zhangxiang 43d1a7af38 chore(repo): 移植 PR-First 提交流程门禁(脚本 + Gitea PR 模板)
对齐 novavis AGENTS.md §27,逐项按本仓实测改写,不照抄不存在的命令:
- scripts/check-pr-checklist.sh:三种模式(模板结构 / 单 PR 文件 / --pr-dir 扫描)
  与退出码 0/1/2 保持同源;模板根改为按脚本自身位置解析并保留 AGENT_PROJECT_DIR
  覆写;修上游 --pr-dir 的参数解析缺陷(for arg in "$@" 内 shift 不消费值,
  目录会被再当成 PR 文件),改 while+shift
- .gitea/PULL_REQUEST_TEMPLATE.md:30 项 checklist 全部换成本仓真实门禁命令
  (type-check / lint / test:unit / test:coverage 阈值 / Playwright / visual /
  check:contrast / check:headings / lighthouse / test:security:headers),并补
  CMS 字段-需重跑 seed 声明、:3000 旧预览不作数、暗黑 token、动效 180–280ms、
  品牌红触达、数字 basis 口径等本仓专有约束
- 文档同源:docs/development/quality-gates.md 新增「提交与 PR 流程」权威副本
  (AGENTS.md 被 .gitignore 排除,不可依赖)、CLAUDE.md 补 Submission Flow 节

门禁自检:脚本 bash -n 通过;11 个用例覆盖三模式 + 缺节/缺文件/子项不足/非 PR
文件/未知选項/--quiet 等负路径,均带正控制以防「匹配零的假绿」。
2026-09-20 10:38:23 +08:00

15 KiB
Raw Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Common Commands

# Development
npm run dev                    # Start dev server on port 3000
npm run dev:clean              # Clean .next/dist then start dev server

# Build & Preview
npm run build                  # Build production files to dist/
npm run build:clean            # Clean then build
npm run preview                # Serve dist/ on port 3000 (npx serve)

# Deploy (统一发布脚本)
./scripts/deploy.sh build                 # 构建静态产物
./scripts/deploy.sh deploy                # 构建并发布到生产服务器
./scripts/deploy.sh deploy --skip-build   # 使用现有 dist/ 直接发布
./scripts/deploy.sh rollback              # 回滚到最近一次远程备份
./scripts/deploy.sh status                # 查看生产环境发布状态

# Linting & Type Checking
npm run lint                   # ESLint (configured in config/lint/.eslintrc.json)
npm run type-check             # tsc --noEmit

# E2E Testing (Playwright)
npm run test                   # Run all E2E tests
npm run test:e2e               # Same as above
npm run test:smoke             # Only @smoke-tagged tests
npm run test:visual            # Visual regression (Desktop Chrome)
npm run test:visual:all        # Visual regression (all projects)
npm run test:visual:update     # Update visual snapshots
npx playwright test --grep "test name"   # Run a single E2E test by name

# Unit Testing (Jest)
npm run test:unit              # Run all unit tests
npm run test:coverage          # Coverage report (thresholds: 80% branches/functions/lines)
npx jest --testPathPattern="button"      # Run a single test file matching pattern

# Quality Checks
npm run check:contrast         # Color contrast audit
npm run check:headings         # Heading hierarchy audit
npm run lighthouse             # Lighthouse CI performance audit

# Database (Prisma + SQLite)
npm run db:seed                # Seed the dev database
npm run db:reset               # Reset database with migrations

Architecture

Tech Stack

  • Next.js 14 (App Router) with hybrid rendering — static pages + API routes (Note: output: 'export' was recently removed; the project is transitioning away from pure static export)
  • React 18, TypeScript 5 (strict mode with noUncheckedIndexedAccess)
  • Tailwind CSS 3 with design tokens exposed as CSS custom properties (all tokenized via var() references in tailwind.config.js)
  • Framer Motion for animations, Lucide React for icons, Zod for validation
  • shadcn/ui pattern (Radix UI + class-variance-authority + tailwind-merge)
  • Prisma with SQLite for backend data (admin, auth, CMS)

Path Alias

@/ maps to src/ — configured in both tsconfig.json (paths) and jest.config.js (moduleNameMapper).

TypeScript Strictness

Beyond strict: true, the project enables:

  • noUncheckedIndexedAccess: true — array/object index access returns T | undefined, requiring null checks. This is a significant constraint to be aware of when writing code.
  • noImplicitReturns, noFallthroughCasesInSwitch, noUnusedLocals, noUnusedParameters

Route Structure (App Router)

src/app/
├── layout.tsx                  # Root layout: fonts, metadata, theme, analytics, SEO schemas
├── (marketing)/                # Route group — all public marketing pages
│   ├── layout.tsx              # Shared: Header + Footer + PageTransition + ErrorBoundary
│   ├── page.tsx                # Home (delegates to home-content-cms.tsx)
│   ├── about/                  # About page (client.tsx)
│   ├── news/                   # News list + [slug] detail
│   ├── contact/                # Contact form
│   ├── products/               # Products hub (HSI model)
│   │   ├── page.tsx            # Product listing (enterprise suites + standalone)
│   │   ├── [id]/               # Product detail (four-layer narrative)
│   │   ├── standalone/[id]/    # Standalone product detail
│   │   ├── erp-upgrade/        # Specific product landing pages
│   │   └── erp-upgrade-v3/
│   ├── services/               # Services list + [id] detail
│   ├── solutions/              # Solutions list + [id] detail (cross-references products)
│   ├── cases/                  # Case studies
│   └── team/                   # Team page
├── api/
│   ├── admin/                  # Admin API
│   ├── auth/                   # Authentication API
│   ├── cms/                    # CMS API routes (draft mode, revalidation)
│   └── contact/                # Contact form submission
├── privacy/, terms/            # Legal pages
└── fonts/                      # Local font files (Geist Sans/Mono)

Archiving Convention

When replacing a page or component with a new version, move the old one to an _archive/ subdirectory (e.g., src/app/(marketing)/_archive/ for old homepage iterations). The archive is excluded from TypeScript compilation via tsconfig.json.

Dark Mode

Dark mode uses the data-theme="dark" HTML attribute (not Tailwind's dark: class). Tailwind is configured with darkMode: ['variant', '[data-theme="dark"] &'] — use the variant form, not ['selector', ...], because the selector form is accepted by the config but JIT emits zero dark: rules under Turbopack + Next.js 16.

Three-state preference model (src/lib/theme.ts is the single source of truth):

Stored value (localStorage['novalon-theme']) Meaning
'light' / 'dark' Explicit user choice — always wins over the OS
'system' / absent Follow OS prefers-color-scheme (default for new visitors)
  • src/app/layout.tsx has an inline <script> in <head> that resolves the preference and sets data-theme before first paint (FOUC prevention). It must stay semantically in sync with src/lib/theme.ts — change both together.
  • src/components/theme/theme-toggle.tsx cycles 跟随系统 → 浅色 → 深色. It listens to matchMedia('(prefers-color-scheme: dark)') for live OS changes (only effective in the system state) and to storage events for cross-tab sync.
  • html[data-theme] is an output of preference resolution — never read it back as the user's preference, or the system state collapses into dark on dark-OS machines and the cycle deadlocks.

HSI Information Architecture

The site follows a Hub-Spoke-Independent model (see CONTEXT.md and ADR-0002):

  • Hub: /products — product catalog, split into "企业套装" (6 enterprise products) and "专业产品" (standalone)
  • Spoke: /solutions — industry scenarios, each recommending product combinations from the Hub
  • Independent: Standalone products in the specialized zone have their own narrative path

Four-Layer Narrative Model

Every detail page (product/service/solution/standalone) follows the same four-layer structure:

  1. L1 Hero: Emotional entry with visual, title, value prop, status badge
  2. L2 Value Rationale: Product-specific — features/benefits for products, pain-points→architecture for solutions, challenges→results for services
  3. L3 Trust Proof: Case studies, data proofs, certifications, testimonials (currently stubbed — company is pre-launch)
  4. L4 CTA Conversion: Primary action + secondary action + cross-recommendations

The detail components implementing this live in src/components/detail/:

  • detail-hero.tsx, detail-product-value.tsx, detail-trust-section.tsx, detail-cta-section.tsx
  • solution-value.tsx, service-value.tsx (type-specific L2 variants)
  • detail-cross-recommend.tsx (cross-links between products↔solutions↔services)

CMS Content Architecture

The app has a mock CMS layer at src/lib/cms/ that simulates a headless CMS (no real backend — designed to be swapped with real API calls later):

  • types.ts — ContentModel, ContentItem, ContentZone, ThemeConfig definitions
  • ContentZoneRenderer.tsx — Renders a zone's items in grid/list/carousel layouts, delegating each item to getItemRenderer(modelCode) from the component registry
  • mock-home.ts — Mock data for the homepage zones
  • component-registry.ts — Maps model codes to React renderer components

The homepage (home-content-cms.tsx) initializes the CMS and fetches mock content zones (hero, stats, services, solutions, cases, news).

Design Token System

All visual tokens are defined as CSS custom properties in src/app/globals.css (:root block) and mapped into Tailwind's config via var() references:

  • Colors: --color-ink (deep charcoal #0A0E14), --color-brand (vermilion #C41E3A), accent colors for service-coding (blue, teal, amber, purple), functional colors (success, warning, error, info), dark-section overrides
  • Typography: --font-size-*, --line-height-*, --letter-spacing-* all mapped to Tailwind's fontSize/lineHeight/letterSpacing scales
  • Spacing, radius, shadows all tokenized through CSS variables
  • Transitions: --transition-fast/normal/slow, --ease-ink (cubic-bezier), --ease-sharp--ease-spring* 死令牌已于 2026-09-19 polish 删除)
  • Dark mode: data-theme="dark" attribute (set via inline script before paint to prevent flash); flipped by the [data-theme='dark'] override block in globals.css, which only re-maps CSS variables — components do not restyle per element

Design DNA Framework

The site follows a three-dimensional design system (see CONTEXT.md):

  • Dimension 1 — Design System: Quantifiable tokens (colors, typography, spacing, radius, shadows, motion, components)
  • Dimension 2 — Design Style: Qualitative perception (atmosphere, visual language, composition, imagery, brand tone)
  • Dimension 3 — Visual Effects: Scroll animations, micro-interactions, parallax, SVG effects

The Consulting Professional aesthetic (inspired by Accenture + Bain) is the primary skeleton. Ink cultural elements (水墨) are secondary decorations limited to ≤6 locations (logo, dividers, transition animations, footer texture).

Brand red (#C41E3A) usage rule: Every page must have ≥3 brand-red touchpoints. It must never be used as paragraph text color, as a large background, or alongside accent colors in the same card. Area ≤10%. Exception: full-bleed dark-red statement/CTA blocks use the separate crimson-veil (#8B1530) token — see DESIGN.md「Crimson Veil」(rule clarified 2026-09-19 critique to resolve this apparent contradiction).

Motion design: Animations are purposeful (not decorative), fast (150-300ms), use ease-ink [0.22, 1, 0.36, 1] as default easing, and stagger children by 30-60ms. No continuous looping animations except pulse-soft for skeletons. No spring easings for content entry. No animations >700ms.

Data Layer

All structured content data is in src/lib/constants/ as TypeScript constants — no API/database for marketing content:

  • products.ts — 6 enterprise products (ERP, CRM, CMS, BI, SDS, OA) + standalone products (NovaVis)
  • services.ts, solutions.ts — Service and solution definitions
  • cases.ts — Case studies with industry filtering
  • navigation.ts — Nav structure + mega dropdown data
  • company.ts, stats.ts, team.ts, news.ts, methodology.ts
  • hero-themes.ts — Per-product hero visual theme variants
  • cross-references.ts — Cross-links between products/solutions/services

Each product/solution/service implements the Product/Solution/Service interface which includes caseStudies[], dataProofs[], certifications[], etc. for the four-layer narrative.

Backend Layer (Prisma + API Routes)

The project has a backend layer for admin/auth/CMS functionality:

  • Prisma with SQLite (prisma/dev.db) for admin user data, auth, and CMS content management
  • API routes at src/app/api/admin/, auth/, cms/, contact/
  • The marketing pages use mock data from src/lib/cms/mock-home.ts, but the CMS API routes suggest a real CMS backend is planned

Component Organization

src/components/
├── ui/              # Base: Button, Card, Input, Badge, ScrollReveal, AnimatedCounter, etc.
├── layout/          # Header, Footer, MobileTabBar, Breadcrumb, MegaDropdownMobileMenu 已删除,抽屉内置于 Header
├── sections/        # Page section components: HeroSectionV2, ServiceGrid, CTASection, etc.
├── detail/          # Four-layer narrative: DetailHero, ProductValueSection, DetailTrustSection, etc.
├── seo/             # Structured data (OrganizationSchema, WebsiteSchema, etc.)
├── analytics/       # GA4, error tracking, cookie consent, scroll depth, outbound links
├── cms/             # CMS renderers (ContentRenderer, SectionRenderer, FieldRenderer)
├── content/         # sections.tsx, testimonials.tsx
└── providers/       # (currently empty)

Testing Setup

  • Jest (unit): Tests alongside source in src/**/*.test.{ts,tsx}, coverage threshold 80%. Config in config/test/jest.config.js. Uses @/ path alias and ts-jest with jsx: 'react-jsx' transform (since tsconfig uses 'preserve'). Run single tests with npx jest --testPathPattern="component-name".
  • Playwright (E2E): Tests in e2e/, config in e2e/playwright.config.ts. Auto-starts npm run preview as web server. Visual regression snapshots in e2e/visual-snapshots/. Three browser projects (chromium, firefox, webkit) plus dedicated visual regression projects at desktop/tablet/mobile breakpoints. Run single tests with npx playwright test --grep "test name".

Build Output

The project builds to dist/ (configured via distDir in next.config.mjs). It is served via Nginx (see nginx-static-production.conf) with CDN support (assetPrefix respects CDN_DOMAIN env var). Images are unoptimized (static export limitation). Note: output: 'export' was recently removed from the config — the project may be moving toward a hybrid SSR + static model.

Commit Convention

Uses Conventional Commits with commitlint (@commitlint/config-conventional). Husky + lint-staged enforces linting on pre-commit.

Submission Flow (PR-First)

dev/main are only reached via feature branch + PR: sync origin/dev → rebase → push → PR gate → merge by Rebase (linear history, no merge commit). bash scripts/check-pr-checklist.sh <pr-description-file> must pass before opening a PR — it verifies .gitea/PULL_REQUEST_TEMPLATE.md has the three required sections (全链路检查 / 测试分层检查 / 质量门禁) with ≥20 checklist items, and that every item in the PR body is checked (mark irrelevant ones as N/A<reason>). Rebase rewrites hashes: an already-pushed branch may only be updated with --force-with-lease. Always rebase onto origin/dev, never a possibly-stale local dev.

Component Versioning History

The project has gone through multiple design iterations. Older component versions:

  • components/detail-v2/ — previous iteration (deleted per git status, migration to detail/ completed)
  • components/detail-v3/ — another iteration (deleted, same reason)
  • home-content-v2.tsx through home-content-v11.tsx — archived homepage iterations in (marketing)/_archive/
  • The current canonical components are in components/detail/ and home-content-cms.tsx