mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-14 16:46:07 +00:00
7078abc14a
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.
3.9 KiB
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 (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.
- No
//line comments in committed TS/TSX. HTML comments are fine. - TS strict;
no-explicit-anyis an error. Build forms withuseZodForm+FormFieldfrom@/components/form/rhf(wrap the tree inFormProvider); validate through thezodResolveror per-fieldrules={{ validate: rhfZodValidate(Schema.shape.field) }}— messages are Zod issue keys resolved viat(), never inlinez.string(). 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).- Register in
src/routes.tsxunder/panel(lazy import). - Add a sidebar link in
src/layouts/AppSidebar.tsxif it needs nav. 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.