RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Diff/mhsanaei-3x-ui-claude ↔ mhsanaei-3x-ui-frontend-claude

Comparison

A · CLAUDE.md · MHSanaei/3x-uiB · CLAUDE.md · MHSanaei/3x-ui
What each file covers, counted
DimensionSharedOnly in AOnly in BOverlap
Sections0860%
Commands316811%
Section tags44240%

What each file covers

Sections

0 shared · 8 only in A · 6 only in B
  • − CLAUDE.md
  • − Stack
  • − Repo map
  • − Hard rules (non-negotiable)
  • − Go conventions
  • − Frontend conventions (summary; full version in frontend/CLAUDE.md)
  • − Build, test, verify
  • − Definition of done (before opening a PR)
  • + frontend/CLAUDE.md
  • + What this is
  • + Data flow
  • + Rules
  • + Adding a panel route
  • + Commands

Commands

3 shared · 16 only in A · 8 only in B
  • − make verify
  • − make gen
  • − make test-go
  • − npm test
  • − make lint
  • − go test ./...
  • − npx playwright install --with-deps chromium
  • − npx vitest run -u
  • − go build ./...
  • − make dist-stub
  • − make
  • − make help
  • − make race
  • − make vulncheck
  • − make test
  • − make build
  • + npm run gen:zod
  • + go run ./tools/openapigen
  • + npm run gen
  • + npm run dev
  • + npm run typecheck
  • + npm run lint
  • + npm run storybook
  • + npm run build-storybook
  •   npm run build
  •   go run .
  •   npm run test

Section tags

4 shared · 4 only in A · 2 only in B
  • − lint-format
  • − code-style
  • − git-pr
  • − deployment
  • + api
  • + docs
  •   build
  •   test
  •   do-not
  •   agent-behaviour

Line diff

+65 added−155 removed7 unchanged4.3% identical
MHSanaei/3x-ui · CLAUDE.md
@@ −1 @@
1# CLAUDE.md
2 
3Operational guide for AI agents working in this repo. Long-form human docs:
4`CONTRIBUTING.md` (setup, testing philosophy) and `frontend/README.md`.
5Read those before large changes. This file is the short, must-follow version.
6For a deep navigation map (request lifecycle, cron-job table, symptom → file
7index, layering rules), read `docs/architecture.md` on demand — do not guess
8file locations when it can answer in one hop.
9 
10## Stack
11- Backend: Go 1.26 (`module github.com/mhsanaei/3x-ui/v3`), Gin, GORM.
12 Runs Xray-core as a managed child process (`internal/xray/process.go`) and
13 imports `github.com/xtls/xray-core` for config types + gRPC stats/handler/router
14 API. MTProto inbounds run a second managed child — the `mtg-multi` binary
15 (a multi-secret mtg fork — NOT a Go dependency; its prebuilt release binary is
16 fetched at image/release build time by `DockerInit.sh` + `release.yml`,
17 panel-side code in `internal/mtproto/`) — outside Xray, one process per inbound
18 serving each
19 client's FakeTLS secret via the fork's `[secrets]` section (plus per-client
20 ad-tags via `[secret-ad-tags]` and per-client data quota / expiry via
21 `[secret-limits]`, mapped from the client's `totalGB`/`expiryTime`). Client,
22 ad-tag and quota/expiry edits are hot-applied through the fork's management API
23 (`PUT /secrets`, bearer-token guarded) so connections survive; the manager
24 falls back to a process restart on older binaries. A client's panel-side
25 traffic reset also calls `POST /secrets/{name}/reset-quota` so a renewed client
26 is not re-blocked by the sidecar's quota counter.
27- Storage: SQLite by default (`/etc/x-ui/x-ui.db` on Linux; the executable dir on
28 Windows), PostgreSQL optional (`XUI_DB_TYPE` / `XUI_DB_DSN`). The CGo SQLite
29 driver (`mattn/go-sqlite3`) needs a C compiler — `CGO_ENABLED=0` builds fail.
30- Frontend: React 19 + Ant Design 6 + Vite 8 + TypeScript in `frontend/`,
31 built into `internal/web/dist/` (gitignored) and embedded via `embed.FS`.
32 
33## Repo map
34- `main.go` — entry point + `x-ui` CLI (run, migrate, migrate-db, setting, cert).
35- `internal/config/` — env parsing (XUI_DEBUG, XUI_LOG_LEVEL, XUI_LOG_FOLDER,
36 XUI_BIN_FOLDER, XUI_SKIP_HSTS, XUI_PORT, XUI_DB_*).
37- `internal/database/` + `internal/database/model/` — GORM schema (~24 models;
38 Inbound, Client, Setting, User are the core), inbound Protocol enum,
39 AutoMigrate + hand-written migrations in `db.go`.
40- `internal/xray/` — Xray child-process lifecycle, config generation, gRPC API.
41- `internal/mtproto/` — MTProto inbounds via the bundled `mtg-multi` binary.
42- `internal/sub/` — subscription server (raw / JSON / Clash).
43- `internal/eventbus/` — in-process pub/sub (outbound/node health, xray.crash,
44 cpu.high, memory.high, login.attempt).
45- `internal/logger/`, `internal/util/` (link, crypto, sys, ldap, …),
46 `internal/tunnelmonitor/` — shared infrastructure.
47- `internal/web/` — Gin server (embeds `dist/` + `translation/`).
48 - `controller/` — panel + REST API handlers; OpenAPI at /panel/api/openapi.json.
49 - `service/` — business logic (InboundService, SettingService, XrayService,
50 node sync); subpackages tgbot/, email/, outbound/, panel/, integration/.
51 - `job/` — 17 cron jobs (traffic, fail2ban IP-limit, node heartbeat/sync, LDAP,
52 CPU/memory watchdogs, …); full table in `docs/architecture.md` §5.4.
53 - `middleware/`, `entity/`, `global/`, `session/` (CSRF), `network/`,
54 `runtime/` (master/sub-node over mTLS), `websocket/`.
55 - `locale/` + `translation/` — i18n, 13 embedded locale JSON files.
56- `frontend/` — React + TS source (see `frontend/CLAUDE.md`).
57- `tools/openapigen/` — Go generator that emits frontend types + Zod/JSON schemas
58 into `frontend/src/generated/` from Go structs. The OpenAPI doc itself
59 (`frontend/public/openapi.json`) is assembled from those + `endpoints.ts` by
60 `frontend/scripts/build-openapi.mjs`. (`tools/seedperf/` is a separate seeding
61 /load helper.)
62- `docs/` — separate Next.js/Fumadocs site (pnpm, own CI in `docs-ci.yml`,
63 outside `make verify`). Holds a THIRD independent implementation of
64 link/subscription generation in `docs/lib/xray/` — check it whenever
65 share-link or install-command output changes.
66 
67## Hard rules (non-negotiable)
68- Fix size must match bug size. Find the root cause, then make the SMALLEST
69 change that removes it — a one-line guard beats a new subsystem. A small bug
70 does not earn new columns, jobs, abstractions, config knobs or helper layers.
71 If a fix genuinely needs new architecture, say so and get agreement first;
72 never ship it unasked next to the fix.
73- Comments in committed Go/TS: 2 lines MAX per comment block. Make the name
74 carry the meaning first and rename rather than annotate; spend the 2 lines on
75 the *why* a name cannot hold — an invariant, an issue number, a non-obvious
76 constraint. Exempt: `//go:build`, `//go:generate`, and other directives.
77 HTML `<!-- -->` is fine. (A linter cannot enforce this — you must.)
78- New `g.POST`/`g.GET` in `internal/web/controller/` REQUIRES a matching entry
79 in `frontend/src/pages/api-docs/endpoints.ts`, then `make gen` (or
80 `cd frontend && npm run gen`). Hand-maintained but pinned both ways by
81 `TestRouteRegistryContract` (`internal/web/routes_contract_test.go`): a missing
82 OR stale entry fails `make test-go`. Scope: `/panel/api/*` + a few session
83 routes; sub-server routes are exempt.
84- Response examples come from Go struct `example:` tags via `tools/openapigen` —
85 never hand-write them. A new struct must be added to openapigen's `StructAllow`
86 allowlist (`tools/openapigen/main.go`) or it is silently omitted from
87 schemas/examples (and `build-openapi.mjs` then fails on the missing schema).
88- A new or renamed endpoint has a FOURTH step nothing checks: copy
89 `frontend/public/openapi.json` → `docs/public/openapi.json`, then
90 `cd docs && pnpm gen:api` to refresh the MDX under
91 `docs/content/docs/en/reference/api/`. `docs-ci.yml` fires only on `docs/**`.
92- A new English i18n key goes in EVERY locale JSON in `internal/web/translation/`
93 (13 files) AND must be referenced from `frontend/src` or Go in the SAME commit —
94 `frontend/src/test/i18n-dead-keys.test.ts` fails both ways. It is a frontend
95 test, so run `npm test`, not just `make test-go`. At runtime the frontend falls
96 back to en-US; Go (`internal/web/locale/`) returns "" for an unknown key.
97- DB / model changes require a migration in `internal/database/db.go`.
98- Every state-changing inbound/client op dispatches through `runtime.Runtime`
99 (`internal/web/runtime/`) — never straight to `internal/xray/api.go`, never from
100 a controller or cron job. A direct call passes every local test and silently
101 breaks every multi-node deployment. Other layering rules: `docs/architecture.md` §8.
102- Conventional commits: `type(area): short imperative summary`, then a body
103 explaining the why. Types in use: `fix`, `feat`, `chore`, `refactor`, `perf`,
104 `docs`, `style`.
105 
106## Go conventions
107- Stdlib `testing` only (no testify). Table-driven, `t.Run` subtests,
108 `t.Helper()` on helpers. Assert the exact value / typed error / emitted
109 string, never just `err != nil`. Prefer real deps over mocks: throwaway DB via
110 `database.InitDB(filepath.Join(t.TempDir(), "x-ui.db"))` +
111 `t.Cleanup(func() { _ = database.CloseDB() })`; `httptest` for HTTP.
112 `internal/sub`'s `initSubDB(t)` is the template.
113- A test must fail without its fix. Write it, revert the fix, watch it go red,
114 restore. A test that passes either way is worse than no test: it certifies
115 nothing and then gets cited as proof the fix works.
116- Test what can actually break. No test for a getter, a constant, a rename, a
117 pure map lookup, or inputs the function can never receive. One real test that
118 drives the bug through the actual code path beats five that restate the code.
119- Code must pass `golangci-lint run` (gofumpt + goimports formatting): `make lint`.
120- Postgres, xray-gRPC-e2e and scale tests `t.Skip` unless `XUI_TEST_PG_DSN`,
121 `XUI_DB_TYPE`+`XUI_DB_DSN`, `XRAY_E2E_BINARY` or `XUI_SCALE_TEST` is set — a
122 green `go test ./...` does not mean those paths ran.
123 
124## Frontend conventions (summary; full version in frontend/CLAUDE.md)
125- Ant Design 6 only — no Tailwind/shadcn. Targeted tweaks, not rewrites.
126- TS strict; `@typescript-eslint/no-explicit-any` is an error. Zod schemas in
127 `src/schemas/` are the source of truth; infer types with `z.infer`, never
128 hand-write. Do not edit `src/generated/`.
129- Node 24 (`.nvmrc`) — `make gen` imports `.ts` directly and needs its type
130 stripping; Node 22 dies with `ERR_UNKNOWN_FILE_EXTENSION`. `npm test` includes
131 a headless-Chromium Storybook project, so run
132 `npx playwright install --with-deps chromium` once or `make verify` fails.
133- Editing `frontend/src` does NOT change what users see until the Vite build is
134 regenerated into `internal/web/dist/`. In `XUI_DEBUG=true`, HTML is served from
135 the frozen embedded FS but JS/CSS off disk — after `npm run build` you MUST
136 restart `go run .` or you get a blank page with 404s.
137- After touching share-link logic (`src/lib/xray/`), run `npm run test` (golden
138 fixtures); regenerate snapshots (`npx vitest run -u`) only for intentional
139 output changes, never to make a red test green.
140 
141## Build, test, verify
142A fresh clone has no `internal/web/dist/`, so a bare `go build ./...` dies with
143`pattern all:dist: no matching files found` while ~35 other packages pass — it
144reads as a broken repo, not a missing step. Run `make dist-stub` once; every
145`make` Go target already depends on it, which is why `make test-go` beats
146`go test ./...`. Run `make help` for all targets. The local gate:
147 
148 make verify # gen-check + lint + typecheck + test + build + build-storybook
149 
150That is the *fast* gate, not all of CI. `ci.yml` also runs `make race`,
151`make vulncheck`, a live-Postgres job (where a SKIP counts as a failure) and a
15230s fuzz smoke on `FuzzParseLink`/`FuzzDecodeCertPin` — run those locally when
153you touch DB/dialect or parser code.
154 
155Common targets: `make gen` (regenerate Zod/OpenAPI), `make lint` (Go + frontend),
156`make test` (Go `-shuffle=on` + frontend), `make race`, `make build`. See `Makefile`.
157 
158## Definition of done (before opening a PR)
1591. `make verify` passes — its `gen-check` already runs `make gen` and fails on a
160 dirty `frontend/src/generated` / `frontend/public/openapi.json`.
1612. Diff is focused; refactors are separate from feature work.
162 
MHSanaei/3x-ui · frontend/CLAUDE.md
@@ +1 @@
1# frontend/CLAUDE.md
2 
3Frontend agent guide. Full detail: `frontend/README.md` and the root
4`CONTRIBUTING.md` ("Working on the frontend"). This is the short version.
 
 
 
 
5 
6## What this is
7React 19 + Ant Design 6 + Vite 8 + TypeScript. The Vite config is
8`vite.config.js` (plain JS). Three bundles, each emitted into
9`internal/web/dist/` and embedded into the Go binary:
10- `index.html` — admin panel SPA (entry `src/main.tsx`; react-router under
11 `/panel`, lazy routes).
12- `login.html` — login + 2FA (`src/entries/login.tsx`).
13- `subpage.html` — public subscription viewer (`src/entries/subpage.tsx`).
14The `@` import alias maps to `src/`.
 
 
 
 
 
 
 
 
 
 
 
 
 
15 
16## Data flow
17- Server state via TanStack Query (`src/api/`, keys in `src/api/queryKeys.ts`);
18 invalidate on mutation. WebSocket pushes feed the cache
19 (`src/api/websocketBridge.ts`).
20- Local UI state in the page (`useState`); shared concerns via `src/hooks/`.
21 Extend an existing hook before adding a global.
22- Zod (`src/schemas/`) is the single source of truth for the xray config model.
23 Infer types with `z.infer`. Go-side types are mirrored into `src/generated/`
24 by `npm run gen:zod` (`go run ./tools/openapigen`) — do not hand-edit that
25 folder (every file is marked `DO NOT EDIT`).
26- xray domain logic (links, defaults, form<->wire adapters) is pure functions in
27 `src/lib/xray/`. HTTP goes through `HttpUtil` in `src/utils/index.ts`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28 
29## Rules
30- Ant Design 6 for components; no Tailwind/shadcn (a migration was rolled back).
31 Form *state* runs on React Hook Form (`src/components/form/rhf/`), not Ant
32 Design's `Form` store.
33- Function components + hooks only; no class components.
34- No `//` line comments in committed TS/TSX. HTML comments are fine.
35- TS strict; `no-explicit-any` is an error. Build forms with `useZodForm` +
36 `FormField` from `@/components/form/rhf` (wrap the tree in `FormProvider`);
37 validate through the `zodResolver` or per-field
38 `rules={{ validate: rhfZodValidate(Schema.shape.field) }}` — messages are Zod
39 issue keys resolved via `t()`, never inline `z.string()`. AntD `<Form>` stays
40 only as a layout wrapper. Complex shared config editors (FinalMask / Sniffing /
41 Sockopt) remain AntD-`Form` islands wrapped as value/onChange adapters in
42 `src/lib/xray/forms/fields/`, bound via a `Controller`.
43- New `g.POST`/`g.GET` route => add it to `src/pages/api-docs/endpoints.ts`,
44 then `npm run gen`.
45- i18n strings live in `internal/web/translation/<locale>.json`, NOT under
46 `frontend/`, and are shared with the Go backend. A new English key must be
47 added to every locale. Interpolation here uses single braces `{var}`, not the
48 i18next default `{{var}}`.
49- Persian/Arabic (RTL) users are first-class — isolate code identifiers on their
50 own line when writing Persian text in labels/toasts.
51- Vite is pinned to an exact version (no `^`) — bump deliberately, then verify
52 `npm run dev` AND `npm run build`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53 
54## Adding a panel route
551. `src/pages/<page>/<Page>.tsx` (kebab folder, PascalCase component).
562. Register in `src/routes.tsx` under `/panel` (lazy import).
573. Add a sidebar link in `src/layouts/AppSidebar.tsx` if it needs nav.
58Only standalone bundles (login/subpage) need a new `.html` + `src/entries/*` +
59`rollupOptions.input` (in `vite.config.js`) + a Go controller route.
 
 
 
 
 
 
 
 
 
 
 
60 
61## Commands
62- `npm run dev` (HMR on :5173, proxies to the Go panel on :2053 — start Go first).
63- `npm run typecheck` / `npm run lint` / `npm run test` / `npm run build`.
64- `npm run gen` = `gen:zod` (Go → `src/generated/`) + `gen:api`
65 (`build-openapi.mjs` → `public/openapi.json`).
66- `npm run storybook` (workbench on :6006) / `npm run build-storybook` (CI
67 compile-checks every story). Reusable `src/components/` get a co-located
68 `<Component>.stories.tsx` with `tags: ['autodocs']`; document props via
69 `argTypes` / `parameters.docs` string metadata, never JSDoc.
70- After `npm run build`, RESTART `go run .` (see the XUI_DEBUG gotcha in root
71 CLAUDE.md) before checking the panel.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72 
@@ −1 +1 @@
1−# CLAUDE.md
1+# frontend/CLAUDE.md
22  
3−Operational guide for AI agents working in this repo. Long-form human docs:
4−`CONTRIBUTING.md` (setup, testing philosophy) and `frontend/README.md`.
5−Read those before large changes. This file is the short, must-follow version.
6−For a deep navigation map (request lifecycle, cron-job table, symptom → file
7−index, layering rules), read `docs/architecture.md` on demand — do not guess
8−file locations when it can answer in one hop.
3+Frontend agent guide. Full detail: `frontend/README.md` and the root
4+`CONTRIBUTING.md` ("Working on the frontend"). This is the short version.
95  
10−## Stack
11−- Backend: Go 1.26 (`module github.com/mhsanaei/3x-ui/v3`), Gin, GORM.
12− Runs Xray-core as a managed child process (`internal/xray/process.go`) and
13− imports `github.com/xtls/xray-core` for config types + gRPC stats/handler/router
14− API. MTProto inbounds run a second managed child — the `mtg-multi` binary
15− (a multi-secret mtg fork — NOT a Go dependency; its prebuilt release binary is
16− fetched at image/release build time by `DockerInit.sh` + `release.yml`,
17− panel-side code in `internal/mtproto/`) — outside Xray, one process per inbound
18− serving each
19− client's FakeTLS secret via the fork's `[secrets]` section (plus per-client
20− ad-tags via `[secret-ad-tags]` and per-client data quota / expiry via
21− `[secret-limits]`, mapped from the client's `totalGB`/`expiryTime`). Client,
22− ad-tag and quota/expiry edits are hot-applied through the fork's management API
23− (`PUT /secrets`, bearer-token guarded) so connections survive; the manager
24− falls back to a process restart on older binaries. A client's panel-side
25− traffic reset also calls `POST /secrets/{name}/reset-quota` so a renewed client
26− is not re-blocked by the sidecar's quota counter.
27−- Storage: SQLite by default (`/etc/x-ui/x-ui.db` on Linux; the executable dir on
28− Windows), PostgreSQL optional (`XUI_DB_TYPE` / `XUI_DB_DSN`). The CGo SQLite
29− driver (`mattn/go-sqlite3`) needs a C compiler — `CGO_ENABLED=0` builds fail.
30−- Frontend: React 19 + Ant Design 6 + Vite 8 + TypeScript in `frontend/`,
31− built into `internal/web/dist/` (gitignored) and embedded via `embed.FS`.
6+## What this is
7+React 19 + Ant Design 6 + Vite 8 + TypeScript. The Vite config is
8+`vite.config.js` (plain JS). Three bundles, each emitted into
9+`internal/web/dist/` and embedded into the Go binary:
10+- `index.html` — admin panel SPA (entry `src/main.tsx`; react-router under
11+ `/panel`, lazy routes).
12+- `login.html` — login + 2FA (`src/entries/login.tsx`).
13+- `subpage.html` — public subscription viewer (`src/entries/subpage.tsx`).
14+The `@` import alias maps to `src/`.
3215  
33−## Repo map
34−- `main.go` — entry point + `x-ui` CLI (run, migrate, migrate-db, setting, cert).
35−- `internal/config/` — env parsing (XUI_DEBUG, XUI_LOG_LEVEL, XUI_LOG_FOLDER,
36− XUI_BIN_FOLDER, XUI_SKIP_HSTS, XUI_PORT, XUI_DB_*).
37−- `internal/database/` + `internal/database/model/` — GORM schema (~24 models;
38− Inbound, Client, Setting, User are the core), inbound Protocol enum,
39− AutoMigrate + hand-written migrations in `db.go`.
40−- `internal/xray/` — Xray child-process lifecycle, config generation, gRPC API.
41−- `internal/mtproto/` — MTProto inbounds via the bundled `mtg-multi` binary.
42−- `internal/sub/` — subscription server (raw / JSON / Clash).
43−- `internal/eventbus/` — in-process pub/sub (outbound/node health, xray.crash,
44− cpu.high, memory.high, login.attempt).
45−- `internal/logger/`, `internal/util/` (link, crypto, sys, ldap, …),
46− `internal/tunnelmonitor/` — shared infrastructure.
47−- `internal/web/` — Gin server (embeds `dist/` + `translation/`).
48− - `controller/` — panel + REST API handlers; OpenAPI at /panel/api/openapi.json.
49− - `service/` — business logic (InboundService, SettingService, XrayService,
50− node sync); subpackages tgbot/, email/, outbound/, panel/, integration/.
51− - `job/` — 17 cron jobs (traffic, fail2ban IP-limit, node heartbeat/sync, LDAP,
52− CPU/memory watchdogs, …); full table in `docs/architecture.md` §5.4.
53− - `middleware/`, `entity/`, `global/`, `session/` (CSRF), `network/`,
54− `runtime/` (master/sub-node over mTLS), `websocket/`.
55− - `locale/` + `translation/` — i18n, 13 embedded locale JSON files.
56−- `frontend/` — React + TS source (see `frontend/CLAUDE.md`).
57−- `tools/openapigen/` — Go generator that emits frontend types + Zod/JSON schemas
58− into `frontend/src/generated/` from Go structs. The OpenAPI doc itself
59− (`frontend/public/openapi.json`) is assembled from those + `endpoints.ts` by
60− `frontend/scripts/build-openapi.mjs`. (`tools/seedperf/` is a separate seeding
61− /load helper.)
62−- `docs/` — separate Next.js/Fumadocs site (pnpm, own CI in `docs-ci.yml`,
63− outside `make verify`). Holds a THIRD independent implementation of
64− link/subscription generation in `docs/lib/xray/` — check it whenever
65− share-link or install-command output changes.
16+## Data flow
17+- Server state via TanStack Query (`src/api/`, keys in `src/api/queryKeys.ts`);
18+ invalidate on mutation. WebSocket pushes feed the cache
19+ (`src/api/websocketBridge.ts`).
20+- Local UI state in the page (`useState`); shared concerns via `src/hooks/`.
21+ Extend an existing hook before adding a global.
22+- Zod (`src/schemas/`) is the single source of truth for the xray config model.
23+ Infer types with `z.infer`. Go-side types are mirrored into `src/generated/`
24+ by `npm run gen:zod` (`go run ./tools/openapigen`) — do not hand-edit that
25+ folder (every file is marked `DO NOT EDIT`).
26+- xray domain logic (links, defaults, form<->wire adapters) is pure functions in
27+ `src/lib/xray/`. HTTP goes through `HttpUtil` in `src/utils/index.ts`.
6628  
67−## Hard rules (non-negotiable)
68−- Fix size must match bug size. Find the root cause, then make the SMALLEST
69− change that removes it — a one-line guard beats a new subsystem. A small bug
70− does not earn new columns, jobs, abstractions, config knobs or helper layers.
71− If a fix genuinely needs new architecture, say so and get agreement first;
72− never ship it unasked next to the fix.
73−- Comments in committed Go/TS: 2 lines MAX per comment block. Make the name
74− carry the meaning first and rename rather than annotate; spend the 2 lines on
75− the *why* a name cannot hold — an invariant, an issue number, a non-obvious
76− constraint. Exempt: `//go:build`, `//go:generate`, and other directives.
77− HTML `<!-- -->` is fine. (A linter cannot enforce this — you must.)
78−- New `g.POST`/`g.GET` in `internal/web/controller/` REQUIRES a matching entry
79− in `frontend/src/pages/api-docs/endpoints.ts`, then `make gen` (or
80− `cd frontend && npm run gen`). Hand-maintained but pinned both ways by
81− `TestRouteRegistryContract` (`internal/web/routes_contract_test.go`): a missing
82− OR stale entry fails `make test-go`. Scope: `/panel/api/*` + a few session
83− routes; sub-server routes are exempt.
84−- Response examples come from Go struct `example:` tags via `tools/openapigen` —
85− never hand-write them. A new struct must be added to openapigen's `StructAllow`
86− allowlist (`tools/openapigen/main.go`) or it is silently omitted from
87− schemas/examples (and `build-openapi.mjs` then fails on the missing schema).
88−- A new or renamed endpoint has a FOURTH step nothing checks: copy
89− `frontend/public/openapi.json` → `docs/public/openapi.json`, then
90− `cd docs && pnpm gen:api` to refresh the MDX under
91− `docs/content/docs/en/reference/api/`. `docs-ci.yml` fires only on `docs/**`.
92−- A new English i18n key goes in EVERY locale JSON in `internal/web/translation/`
93− (13 files) AND must be referenced from `frontend/src` or Go in the SAME commit —
94− `frontend/src/test/i18n-dead-keys.test.ts` fails both ways. It is a frontend
95− test, so run `npm test`, not just `make test-go`. At runtime the frontend falls
96− back to en-US; Go (`internal/web/locale/`) returns "" for an unknown key.
97−- DB / model changes require a migration in `internal/database/db.go`.
98−- Every state-changing inbound/client op dispatches through `runtime.Runtime`
99− (`internal/web/runtime/`) — never straight to `internal/xray/api.go`, never from
100− a controller or cron job. A direct call passes every local test and silently
101− breaks every multi-node deployment. Other layering rules: `docs/architecture.md` §8.
102−- Conventional commits: `type(area): short imperative summary`, then a body
103− explaining the why. Types in use: `fix`, `feat`, `chore`, `refactor`, `perf`,
104− `docs`, `style`.
29+## Rules
30+- Ant Design 6 for components; no Tailwind/shadcn (a migration was rolled back).
31+ Form *state* runs on React Hook Form (`src/components/form/rhf/`), not Ant
32+ Design's `Form` store.
33+- Function components + hooks only; no class components.
34+- No `//` line comments in committed TS/TSX. HTML comments are fine.
35+- TS strict; `no-explicit-any` is an error. Build forms with `useZodForm` +
36+ `FormField` from `@/components/form/rhf` (wrap the tree in `FormProvider`);
37+ validate through the `zodResolver` or per-field
38+ `rules={{ validate: rhfZodValidate(Schema.shape.field) }}` — messages are Zod
39+ issue keys resolved via `t()`, never inline `z.string()`. AntD `<Form>` stays
40+ only as a layout wrapper. Complex shared config editors (FinalMask / Sniffing /
41+ Sockopt) remain AntD-`Form` islands wrapped as value/onChange adapters in
42+ `src/lib/xray/forms/fields/`, bound via a `Controller`.
43+- New `g.POST`/`g.GET` route => add it to `src/pages/api-docs/endpoints.ts`,
44+ then `npm run gen`.
45+- i18n strings live in `internal/web/translation/<locale>.json`, NOT under
46+ `frontend/`, and are shared with the Go backend. A new English key must be
47+ added to every locale. Interpolation here uses single braces `{var}`, not the
48+ i18next default `{{var}}`.
49+- Persian/Arabic (RTL) users are first-class — isolate code identifiers on their
50+ own line when writing Persian text in labels/toasts.
51+- Vite is pinned to an exact version (no `^`) — bump deliberately, then verify
52+ `npm run dev` AND `npm run build`.
10553  
106−## Go conventions
107−- Stdlib `testing` only (no testify). Table-driven, `t.Run` subtests,
108− `t.Helper()` on helpers. Assert the exact value / typed error / emitted
109− string, never just `err != nil`. Prefer real deps over mocks: throwaway DB via
110− `database.InitDB(filepath.Join(t.TempDir(), "x-ui.db"))` +
111− `t.Cleanup(func() { _ = database.CloseDB() })`; `httptest` for HTTP.
112− `internal/sub`'s `initSubDB(t)` is the template.
113−- A test must fail without its fix. Write it, revert the fix, watch it go red,
114− restore. A test that passes either way is worse than no test: it certifies
115− nothing and then gets cited as proof the fix works.
116−- Test what can actually break. No test for a getter, a constant, a rename, a
117− pure map lookup, or inputs the function can never receive. One real test that
118− drives the bug through the actual code path beats five that restate the code.
119−- Code must pass `golangci-lint run` (gofumpt + goimports formatting): `make lint`.
120−- Postgres, xray-gRPC-e2e and scale tests `t.Skip` unless `XUI_TEST_PG_DSN`,
121− `XUI_DB_TYPE`+`XUI_DB_DSN`, `XRAY_E2E_BINARY` or `XUI_SCALE_TEST` is set — a
122− green `go test ./...` does not mean those paths ran.
54+## Adding a panel route
55+1. `src/pages/<page>/<Page>.tsx` (kebab folder, PascalCase component).
56+2. Register in `src/routes.tsx` under `/panel` (lazy import).
57+3. Add a sidebar link in `src/layouts/AppSidebar.tsx` if it needs nav.
58+Only standalone bundles (login/subpage) need a new `.html` + `src/entries/*` +
59+`rollupOptions.input` (in `vite.config.js`) + a Go controller route.
12360  
124−## Frontend conventions (summary; full version in frontend/CLAUDE.md)
125−- Ant Design 6 only — no Tailwind/shadcn. Targeted tweaks, not rewrites.
126−- TS strict; `@typescript-eslint/no-explicit-any` is an error. Zod schemas in
127− `src/schemas/` are the source of truth; infer types with `z.infer`, never
128− hand-write. Do not edit `src/generated/`.
129−- Node 24 (`.nvmrc`) — `make gen` imports `.ts` directly and needs its type
130− stripping; Node 22 dies with `ERR_UNKNOWN_FILE_EXTENSION`. `npm test` includes
131− a headless-Chromium Storybook project, so run
132− `npx playwright install --with-deps chromium` once or `make verify` fails.
133−- Editing `frontend/src` does NOT change what users see until the Vite build is
134− regenerated into `internal/web/dist/`. In `XUI_DEBUG=true`, HTML is served from
135− the frozen embedded FS but JS/CSS off disk — after `npm run build` you MUST
136− restart `go run .` or you get a blank page with 404s.
137−- After touching share-link logic (`src/lib/xray/`), run `npm run test` (golden
138− fixtures); regenerate snapshots (`npx vitest run -u`) only for intentional
139− output changes, never to make a red test green.
140− 
141−## Build, test, verify
142−A fresh clone has no `internal/web/dist/`, so a bare `go build ./...` dies with
143−`pattern all:dist: no matching files found` while ~35 other packages pass — it
144−reads as a broken repo, not a missing step. Run `make dist-stub` once; every
145−`make` Go target already depends on it, which is why `make test-go` beats
146−`go test ./...`. Run `make help` for all targets. The local gate:
147− 
148− make verify # gen-check + lint + typecheck + test + build + build-storybook
149− 
150−That is the *fast* gate, not all of CI. `ci.yml` also runs `make race`,
151−`make vulncheck`, a live-Postgres job (where a SKIP counts as a failure) and a
152−30s fuzz smoke on `FuzzParseLink`/`FuzzDecodeCertPin` — run those locally when
153−you touch DB/dialect or parser code.
154− 
155−Common targets: `make gen` (regenerate Zod/OpenAPI), `make lint` (Go + frontend),
156−`make test` (Go `-shuffle=on` + frontend), `make race`, `make build`. See `Makefile`.
157− 
158−## Definition of done (before opening a PR)
159−1. `make verify` passes — its `gen-check` already runs `make gen` and fails on a
160− dirty `frontend/src/generated` / `frontend/public/openapi.json`.
161−2. Diff is focused; refactors are separate from feature work.
61+## Commands
62+- `npm run dev` (HMR on :5173, proxies to the Go panel on :2053 — start Go first).
63+- `npm run typecheck` / `npm run lint` / `npm run test` / `npm run build`.
64+- `npm run gen` = `gen:zod` (Go → `src/generated/`) + `gen:api`
65+ (`build-openapi.mjs` → `public/openapi.json`).
66+- `npm run storybook` (workbench on :6006) / `npm run build-storybook` (CI
67+ compile-checks every story). Reusable `src/components/` get a co-located
68+ `<Component>.stories.tsx` with `tags: ['autodocs']`; document props via
69+ `argTypes` / `parameters.docs` string metadata, never JSDoc.
70+- After `npm run build`, RESTART `go run .` (see the XUI_DEBUG gotcha in root
71+ CLAUDE.md) before checking the panel.
16272  
RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack