steelforesight/CLAUDE.md

6.2 KiB

CLAUDE.md

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

What this is

Two independent apps in one repo:

  1. Frontend (repo root) — a Vite + React 19 + TypeScript marketing/content site for "اندیشکده فولاد آینده" (a steel-industry think tank). Persian-first, RTL, bilingual (fa/en). Deploys as a static build via Docker to Hugging Face Spaces on port 7860.
  2. Admin panel (panel/) — a standalone Node/Express + SQLite app with its own package.json and node_modules. Manages content the site is meant to publish. The frontend does NOT consume the panel yet — every section renders hardcoded mock data. Wiring them is a deliberate future step.

These two have no shared build or dependency tree. Treat panel/ as a separate project.

Commands

Frontend (run from repo root):

npm run dev          # Vite dev server (HMR)
npm run build        # tsc -b && vite build  (type-checks then bundles)
npm run lint         # eslint .
npm run preview      # serve the production build
npx tsc -b --noEmit  # type-check only — the fast inner loop; use after every edit

There is no test framework configured. "Verifying" a change means npx tsc -b --noEmit (must be clean) and looking at the running dev server.

Admin panel (run from panel/):

cd panel
npm install                 # first time only
npm run seed                # create the admin user from .env (ADMIN_USERNAME/PASSWORD)
npm start                   # node server.js → http://localhost:3001
npm run dev                 # node --watch server.js
npm rebuild better-sqlite3  # REQUIRED if Node's ABI changed — native addon, fails to load otherwise

Critical build gotcha

panel/ lives inside the frontend's project root, so Vite's dependency scanner reads panel/package.json. If that file (or anything Vite scans) has unresolved git merge-conflict markers (<<<<<<<), npm run dev/build fails with a cryptic JSONError ... expected value at line 1. Keep panel/package.json valid JSON.

Frontend architecture

  • Routing: src/app/router.tsx (createBrowserRouter). RootLayout (src/app/layout/RootLayout.tsx) wraps every page with Header, Footer, and the smooth-scroll engine. Pages live in src/pages/<Name>/<Name>.tsx; the homepage composes section components from src/pages/Home/sections/.
  • Language: useLang() from src/context/LangContext returns { lang: 'fa'|'en', toggle }. Components hold inline { fa: {...}, en: {...} } objects and index by lang. RTL is driven by dir={lang==='fa'?'rtl':'ltr'} near the page root; many flex containers force direction:'ltr' locally to stop RTL from reversing their child order, then set inner text back to rtl.
  • Smooth scroll: RootLayout runs Lenis synced to the GSAP ticker (the joinspread.app recipe) — one shared RAF clock so scroll-driven animations don't stutter. It is desktop-only: on touch/coarse pointers it hands off to native momentum (avoids the iOS double-smoothing lag). Do not re-add CSS scroll-behavior: smooth — it fights Lenis.
  • Hero (src/pages/Home/sections/HeroSection.tsx): a GSAP ScrollTrigger scrub timeline pinned over a 200vh section (de-zoom + satellite scatter). This is GSAP, not framer-motion.
  • Styling: mostly inline style objects, with Tailwind utility classes for responsive overrides (max-md:, max-lg:) and a few !important overrides. Global CSS variables in src/index.css (--ink, --paper, --red, --color-orange, etc.). Brand colors are gold #CD9E53 and navy #032340 — these are also hardcoded as const GOLD/const NAVY in many section files, so a rebrand is a repo-wide find-and-replace, not just a CSS-var change.
  • Globe (src/components/ui/globe.tsx): three-globe mounted in react-three-fiber via <primitive> (NOT via extend/JSX-element augmentation — that augmentation poisons global JSX types and breaks unrelated components like React.ElementType icons). Country polygons load at runtime from public/globe-countries.geojson (bundled, public-domain Natural Earth).
  • Calendar (src/components/ui/EventCalendar.tsx): a native Jalali (Persian) month grid built with Intl.DateTimeFormat('en-US-u-ca-persian', …)no date library. It walks Gregorian Dates and reads their Persian parts to lay out the grid.
  • Content layer: src/content/*.ts and inline arrays hold all section data. These are mock/seed data and are explicitly intended to be replaced by fetch calls to the panel API later (see "Wiring", below). Keep new editorial data in this shape so the swap stays mechanical.

Admin panel architecture (panel/)

  • Stack: Express 4 + better-sqlite3 (single file panel/data.db), bcryptjs + jsonwebtoken (HTTP-only cookie named session), multer uploads to panel/uploads/. Admin UI is a vanilla-JS SPA in panel/public/ (app.js, no build step).
  • Schema: panel/db.js (users, articles, risk_signals, prices) plus row→object mappers. articles is a flexible universal content model discriminated by category/type.
  • API (panel/server.js): public reads (GET /api/articles, /api/risks, /api/prices) + auth-gated writes via the authRequired middleware. CRUD for articles, risk signals, and users; /api/uploads; /api/contact. CORS is gated by the ALLOWED_ORIGINS env var.
  • Prices are scraped from tgju.org by panel/scraper.js on a 120s interval when the server runs.
  • Config: panel/.env (copy from .env.example) — JWT_SECRET, ADMIN_*, PORT (3001), ALLOWED_ORIGINS, mail creds.

Wiring the panel to the site (not done yet)

When asked to make published content appear on the site: add a frontend fetch client (e.g. src/lib/api.ts reading import.meta.env.VITE_API_URL), replace a section's src/content/* import with a hook that calls GET /api/articles?category=<section>, and keep the static file as the loading fallback. Do it one section at a time. The articles table's category column is the per-section discriminator. Recommended schema additions before going live: a status (draft/published) column, and an events resource for the calendar.