Files
hudozka/CLAUDE.md
T
2026-02-10 00:37:21 +07:00

111 lines
7.5 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.
## Project Overview
Website for the Art School of Shlisselburg ([art.shlisselburg.org](https://art.shlisselburg.org)). Content is in Russian. Built with Next.js using the App Router and Strapi headless CMS as the backend. The project has migrated through Express → Angular 1 → React class components → Next.js (Pages Router) → Next.js (App Router); some legacy artifacts remain.
## Commands
- `npm run dev` — dev server with Turbo (`next dev --turbo`)
- `npm run build` — production build
- `npm run lint` — ESLint checks
- `npm run format` — auto-fix lint issues
- `npm test` — run unit tests (Vitest, non-watch mode)
Node 24 required (see `mise.toml`). Deployed on Vercel.
## Git Workflow
Feature branches are named `issue-XXX` where `XXX` is the related GitHub issue number. PR descriptions should not include a test plan section.
## CI/CD
- `Dockerfile` — multi-stage Alpine build with standalone Next.js output, pushed to GHCR
- `.github/workflows/build-app.yaml` — builds and pushes Docker image on push to master
- `.github/workflows/test-app.yml` — runs lint, tests, and build on PRs (build runs only after lint and tests pass)
## Code Style
ESLint 9 flat config (`eslint.config.js`) with native `eslint-config-next` 16.x support. Extends `next/core-web-vitals`. Enforces: no semicolons, double quotes, 4-space indentation, always-multiline trailing commas, `eol-last`.
**React components:** Use regular function declarations (not arrow functions) and `type` for props. Do not use `React.FC`.
```tsx
export type MyComponentProps = {
value: string
}
export function MyComponent({ value }: MyComponentProps) {
return <div>{value}</div>
}
```
## Dependencies
When adding or updating packages in `package.json`, do not specify the PATCH version — use `^MAJOR.MINOR.0`. The exact patch version is managed by the lockfile and `npm update`.
## Architecture
**App Router with SSG/ISR:** Pages are server components in the `app/` directory with `export const revalidate = 30`. The root layout (`app/layout.tsx`) fetches menu data and provides it via `MenuContext` through a `Providers` client component. The catch-all route `app/[...slug]/page.tsx` handles all dynamic content pages with `generateStaticParams` and `generateMetadata`.
**Server/client boundary:** Layout and page components are server components that fetch data. The `App` component (`src/components/App`) is a `"use client"` component that provides the layout chrome (navigation, breadcrumbs, footer). Content is passed as `children` from server pages through the client boundary. Components using hooks (`PageGrid`, `HomeContent`, `PageContent`, `Menu`, `MobileNavigation`, `Wrapper`) are marked `"use client"`.
**Data flow:** Strapi CMS API (`hudozka.tmshv.com`) → `src/remote/api.ts` fetches data via `apiGet()` generic wrapper → `src/remote/factory.ts` normalizes CMS responses into app types (`src/types.ts`) → pages render token-based content blocks. The API layer uses a factory pattern with default-response fallbacks on failure.
**Metadata:** Uses the Next.js Metadata API (`generateMetadata`, `export const metadata`). The `buildMetadata()` helper in `src/lib/meta.ts` bridges the app's `Meta` type to Next.js `Metadata` format.
**Content tokens:** Pages are composed of flexible content blocks (tokens): `text`, `image`, `file`, `html`, `youtube`, `instagram`, `grid`. The `Token` discriminated union is defined in `src/types.ts`. Each token type maps to a renderer component in the `PageContent` client component.
**State management:** Valtio for reactive state (`src/store/`). Theme/accessibility preferences in `theme` store, playback state in `play` store, school contact info and year range in `config` store. Navigation data passed via React Context (`src/context/MenuContext`), provided by the root layout.
## Key Directories
- `app/` — App Router pages, layouts, and route handlers (layout, home page, `[...slug]` catch-all, not-found, sitemap, robots, feed)
- `src/components/` — page-level and feature components (folder-per-component with `index.tsx` + `styles.module.css`)
- `src/ui/` — reusable primitives (Box, Button, Picture, Panel, Overlay, Title)
- `src/remote/` — Strapi API integration (`api.ts` for fetching, `factory.ts` for transforms, `types.ts` for CMS response shapes)
- `src/store/` — Valtio stores (theme/accessibility, playback config)
- `src/hooks/` — custom hooks (`useMobile`, `useAccessibility`, `useDarkTheme`, `useReducedMotion`)
- `src/lib/` — pure utility functions (date, string, url, image, file helpers)
- `src/style/` — global CSS with CSS custom properties for theming
- `modules/` — ancillary packages (strapi API client, sync tools, writer); excluded from tsconfig
## Styling
CSS Modules (`.module.css`) for component-scoped styles. Global theme via CSS custom properties in `src/style/style.css`. No utility CSS framework. Uses `classnames` package for conditional class composition.
## TypeScript
Path alias: `@/*` → `src/*`. Strict mode enabled. Target ES2020. `moduleDetection: "force"` in tsconfig (required because Next.js auto-generates `.next/types/validator.ts` without exports, which breaks `verbatimModuleSyntax`). Use `@/` path alias for all local imports, not `src/`. Legacy `src/` imports exist in the codebase but should not be used in new or modified code.
`verbatimModuleSyntax` is enabled — use `import type` for type-only imports. When importing both values and types from the same module, use separate `import` and `import type` statements:
```ts
import { createPage } from "@/remote/factory"
import type { Page } from "@/types"
```
## Accessibility
The site has an accessibility panel with configurable font size (8-48pt), color schemes (5 presets: black-on-white, white-on-black, blue-on-blue, brown-on-yellow, green-on-brown), line height, letter spacing, image toggling, and serif/sans-serif font choice — all managed through the Valtio theme store. The theme store currently applies changes via direct DOM manipulation in a Valtio `subscribe()` callback (`src/store/theme.ts`).
## Images
Remote images from `hudozkacdn.tmshv.com` with `images.weserv.nl` as proxy. Blur hash encoding for progressive loading (uses `sharp` server-side). Image optimization configured in `next.config.ts` via `remotePatterns`.
## Testing
Vitest for unit tests. Config in `vitest.config.ts` with `@/` and `src/` path aliases. Test files are co-located with source across `src/lib/`, `src/ui/`, and `src/components/`. Covers utility functions (array, string, url, file, image, date, text, meta), UI primitives (Box, Button, Panel), and components (Breadcrumbs, YMetrika).
## Known Legacy / Technical Debt
Active modernization is tracked in GitHub issues #222–#238. Key items:
- `src/types.ts` has `I`-prefix interfaces (`IPage`, `IMenu`, etc.) from the Angular era alongside modern `type` aliases. The `tokens` field on `IPage` is `any[]` despite a proper `Token` union type existing.
- `src/store/theme.ts` manipulates DOM directly outside React lifecycle (sets CSS vars on `document.documentElement`, toggles `document.body` classes).
- Two orphaned `.scss` files in `src/style/` (`tape-viewer.scss`, `vertical-image.scss`) — not imported anywhere, no SCSS preprocessor configured.
- Some inline styles in `src/components/App/index.tsx` should be CSS modules.
- `react-use` and `use-media` dependencies are unmaintained; only `useToggle` and `useLockBodyScroll` are used from `react-use`.