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

72 lines
3.9 KiB
Markdown

# 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.mjs``public/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.