mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-25 15:25:01 +03:00
14c9bdff89
The router, the sidebar, the page titles and the command palette each kept their own list of the panel's pages, so a page added to one could go missing from another without any test noticing. They all read src/routes/panelRoutes.tsx now, and a contract test holds the manifest to the router, the translation bundle and the palette keywords. The unused useZodForm helper goes, and CONTRIBUTING, frontend/CLAUDE.md and the frontend README describe the useForm + rhfZodValidate pattern the panel's forms actually use.
4.2 KiB
4.2 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 (entrysrc/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 tosrc/.
Data flow
- Server state via TanStack Query (
src/api/, keys insrc/api/queryKeys.ts); invalidate on mutation. WebSocket pushes feed the cache (src/api/websocketBridge.ts). - Local UI state in the page (
useState); shared concerns viasrc/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 withz.infer. Go-side types are mirrored intosrc/generated/bynpm run gen:zod(go run ./tools/openapigen) — do not hand-edit that folder (every file is markedDO NOT EDIT). - xray domain logic (links, defaults, form<->wire adapters) is pure functions in
src/lib/xray/. HTTP goes throughHttpUtilinsrc/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'sFormstore. - Function components + hooks only; no class components.
- Comments in committed TS/TSX: 2 lines MAX per comment block, spent on the why a name cannot hold (same rule as root CLAUDE.md). HTML comments are fine.
- TS strict; oxlint's
typescript/no-explicit-anyis an error. Build forms with React Hook Form'suseForm+FormFieldfrom@/components/form/rhf(wrap the tree inFormProvider) and validate each field withrules={{ validate: rhfZodValidate(Schema.shape.field) }}— messages are Zod issue keys resolved viat(), never inlinez.string(). A whole-formzodResolveris only for a form that submits one schema. AntD<Form>stays only as a layout wrapper. Complex shared config editors (FinalMask / Sniffing / Sockopt) remain AntD-Formislands wrapped as value/onChange adapters insrc/lib/xray/forms/fields/, bound via aController. - New
g.POST/g.GETroute => add it tosrc/pages/api-docs/endpoints.ts, thennpm run gen. - i18n strings live in
internal/web/translation/<locale>.json, NOT underfrontend/, 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 verifynpm run devANDnpm run build.
Adding a panel route
src/pages/<page>/<Page>.tsx(kebab folder, PascalCase component).- Add it to
src/routes/panelRoutes.tsx. The router, the sidebar, the page title and the command palette all read that one list;src/test/navigation-contract.test.tsxholds it to the router and to the translation bundle. Only standalone bundles (login/subpage) need a new.html+src/entries/*+rollupOptions.input(invite.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.mjs→public/openapi.json).npm run storybook(workbench on :6006) /npm run build-storybook(CI compile-checks every story). Reusablesrc/components/get a co-located<Component>.stories.tsxwithtags: ['autodocs']; document props viaargTypes/parameters.docsstring metadata, never JSDoc.- After
npm run build, RESTARTgo run .(see the XUI_DEBUG gotcha in root CLAUDE.md) before checking the panel.