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

216 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Common Commands
```bash
# 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`