Files
3x-ui/frontend/CLAUDE.md
T
MHSanaei 7078abc14a feat(frontend): make Storybook a validated, fully covered component workbench
Storybook existed only as an undocumented local tool: 9 of 24 reusable components had stories, autodocs pages were bare prop tables, nothing built or tested the stories, and no contributor doc mentioned the workbench existed.

Every reusable component under src/components/ now has a co-located story with enriched autodocs (component descriptions plus per-prop argTypes, kept as string metadata since the repo bans line comments). Stories double as headless Chromium tests through the Storybook vitest addon, with axe accessibility checks enforced as errors and play-function interaction tests covering the modals, the RHF field bridge, the config block, and the select-all buttons. The preview now mirrors the panel's real theme DOM (body class, shared AntD theme config, seeded theme storage) so what stories render matches production.

CI and make verify gain a static Storybook build as a compile gate, and the frontend test job installs Chromium so story tests run on every PR. Contributor docs (frontend README, CONTRIBUTING, agent guides) document the workbench, the story conventions, and the Controls setup. Node engines move to 24 LTS and gen:api drops the type-stripping flags that Node 24 makes default.
2026-07-14 03:37:21 +02:00

3.9 KiB

frontend/CLAUDE.md

Frontend agent guide. Full detail: frontend/README.md and the root CONTRIBUTING.md ("Working on the frontend"). This is the short version.

What this is

React 19 + Ant Design 6 + Vite 8 + TypeScript. The Vite config is vite.config.js (plain JS). Three bundles, each emitted into internal/web/dist/ and embedded into the Go binary:

  • index.html — admin panel SPA (entry src/main.tsx; react-router under /panel, lazy routes).
  • login.html — login + 2FA (src/entries/login.tsx).
  • subpage.html — public subscription viewer (src/entries/subpage.tsx). The @ import alias maps to src/.

Data flow

  • Server state via TanStack Query (src/api/, keys in src/api/queryKeys.ts); invalidate on mutation. WebSocket pushes feed the cache (src/api/websocketBridge.ts).
  • Local UI state in the page (useState); shared concerns via src/hooks/. Extend an existing hook before adding a global.
  • Zod (src/schemas/) is the single source of truth for the xray config model. Infer types with z.infer. Go-side types are mirrored into src/generated/ by npm run gen:zod (go run ./tools/openapigen) — do not hand-edit that folder (every file is marked DO NOT EDIT).
  • xray domain logic (links, defaults, form<->wire adapters) is pure functions in src/lib/xray/. HTTP goes through HttpUtil in src/utils/index.ts.

Rules

  • Ant Design 6 for components; no Tailwind/shadcn (a migration was rolled back). Form state runs on React Hook Form (src/components/form/rhf/), not Ant Design's Form store.
  • Function components + hooks only; no class components.
  • No // line comments in committed TS/TSX. HTML comments are fine.
  • TS strict; no-explicit-any is an error. Build forms with useZodForm + FormField from @/components/form/rhf (wrap the tree in FormProvider); validate through the zodResolver or per-field rules={{ validate: rhfZodValidate(Schema.shape.field) }} — messages are Zod issue keys resolved via t(), never inline z.string(). AntD <Form> stays only as a layout wrapper. Complex shared config editors (FinalMask / Sniffing / Sockopt) remain AntD-Form islands wrapped as value/onChange adapters in src/lib/xray/forms/fields/, bound via a Controller.
  • New g.POST/g.GET route => add it to src/pages/api-docs/endpoints.ts, then npm run gen.
  • i18n strings live in internal/web/translation/<locale>.json, NOT under frontend/, and are shared with the Go backend. A new English key must be added to every locale. Interpolation here uses single braces {var}, not the i18next default {{var}}.
  • Persian/Arabic (RTL) users are first-class — isolate code identifiers on their own line when writing Persian text in labels/toasts.
  • Vite is pinned to an exact version (no ^) — bump deliberately, then verify npm run dev AND npm run build.

Adding a panel route

  1. src/pages/<page>/<Page>.tsx (kebab folder, PascalCase component).
  2. Register in src/routes.tsx under /panel (lazy import).
  3. Add a sidebar link in src/layouts/AppSidebar.tsx if it needs nav. Only standalone bundles (login/subpage) need a new .html + src/entries/* + rollupOptions.input (in vite.config.js) + a Go controller route.

Commands

  • npm run dev (HMR on :5173, proxies to the Go panel on :2053 — start Go first).
  • npm run typecheck / npm run lint / npm run test / npm run build.
  • npm run gen = gen:zod (Go → src/generated/) + gen:api (build-openapi.mjspublic/openapi.json).
  • npm run storybook (workbench on :6006) / npm run build-storybook (CI compile-checks every story). Reusable src/components/ get a co-located <Component>.stories.tsx with tags: ['autodocs']; document props via argTypes / parameters.docs string metadata, never JSDoc.
  • After npm run build, RESTART go run . (see the XUI_DEBUG gotcha in root CLAUDE.md) before checking the panel.