# 3x-ui Frontend Zod Migration — Status Branch: `feat/frontend-zod-validation` · 83 commits ahead of `main` Last updated: 2026-05-26 ## What this is The work tracked here is the migration described in `C:\Users\Hossein Sanaei\.claude\plans\zod-soft-feather.md` — replacing the class-based xray models (`models/inbound.ts`, `models/outbound.ts`) with Zod schemas as the single source of truth, standardizing every form on AntD `Form.useForm` + `antdRule(schema.shape.X)`, and tightening `@typescript-eslint/no-explicit-any` to `error`. Verify state: `npm run typecheck` clean, `npm run lint` clean, `npm run test` 302/302, snapshot baselines 172/172. --- ## Done ### Foundations - API-boundary Zod validation in TanStack Query hooks (`parseMsg` helper) - Backend request-body validation via `go-playground/validator` - Go-first codegen tool (`tools/openapigen`) emitting `zod.ts` + `types.ts` - `antdRule(schema)` helper bridging Zod issues to AntD form rules - Five secondary modals migrated to Pattern A (Login, 2FA, Geo, Balancer, Rule) - Pre-save schema guard on Inbound/Outbound form submits ### Schemas — `frontend/src/schemas/` - `primitives/` — port, protocol, sniffing, atomic dictionaries - `protocols/inbound/*` — 10 protocols as leaf schemas - `protocols/outbound/*` — 11 protocols as leaf schemas - `protocols/stream/*` — 7 networks (tcp/kcp/ws/grpc/httpupgrade/xhttp/hysteria) - `protocols/security/*` — 3 securities (none/tls/reality) - `forms/inbound-form.ts` — `InboundFormValues` discriminated union - `forms/outbound-form.ts` — `OutboundFormValues` discriminated union - Stream + security families wired as `z.discriminatedUnion` with intersection ### Pure-function ports — `frontend/src/lib/xray/` - `headers.ts` — `toHeaders`, `toV2Headers`, `getHeaderValue` - `inbound-link.ts` — `genVmessLink`, `genVlessLink`, `genTrojanLink`, `genShadowsocksLink`, `genHysteriaLink`, Wireguard link/config - `outbound-link-parser.ts` — vmess/vless/trojan/shadowsocks/hysteria2 - `inbound-defaults.ts` — `createDefault{Vmess,Vless,...}{Client,InboundSettings}` - `outbound-defaults.ts` — settings factories + dispatcher - `outbound-form-adapter.ts` — raw ↔ `OutboundFormValues` round-trip - `protocol-capabilities.ts` — capability predicates as pure functions ### Form modals on Pattern A - `InboundFormModal.tsx` — full rewrite, atomic-swapped from `.new.tsx` - Tabs: Basic, Sniffing, Protocol, Stream, Security, Advanced JSON, Fallbacks - All 10 protocols (VLESS, VMess, Trojan, Shadowsocks, HTTP, Mixed, Tunnel, TUN, Wireguard, Hysteria) - Full Stream tab (TCP, KCP, WS, gRPC, HTTPUpgrade, XHTTP, Hysteria) - Full Security tab (TLS list, Reality, ECH, mldsa65) - 18-field sockopt section, full TLS cert list, external-proxy section - `OutboundFormModal.tsx` — full rewrite, atomic-swapped from `.new.tsx` - All 12 protocols (vmess/vless/trojan/shadowsocks/socks/http/hysteria/ freedom/blackhole/dns/loopback/wireguard) - Full Stream tab with XHTTP advanced fields + xmux sub-form - Full Security tab (TLS + Reality + Vision flow) - Sockopt section (17 knobs) - Mux section - JSON tab for advanced fields - Link import (vmess/vless/trojan/ss/hysteria2) with full XHTTP round-trip (padding obfs + session/seq/uplink keys + all post-size knobs) - `FinalMaskForm` rewritten to Pattern A (Form.List-driven) and wired into both stream tabs (Inbound + Outbound). Covers TCP/UDP mask arrays, all 13 UDP mask types, header-custom nested groups, noise items, and the QUIC params sub-form. ### Tests - Golden-file fixture suite (`test/golden/fixtures/`) - Snapshot-baseline regression tests for inbound-full / outbound / stream / security DUs - Shadow-parse harness asserting legacy class and Zod converge - Link-parser tests (15 round-trip cases including XHTTP padding-obfs) - Outbound form-adapter tests (15 round-trip cases) - 302 tests across 12 files, 172 snapshots ### Build infrastructure - `@typescript-eslint/no-explicit-any: 'error'` enforced - `.github/workflows/ci.yml` runs `typecheck` + `test` before `build` - Vite pinned to 8.0.13 (dev-mode dep-optimizer regression in 8.0.14) --- ## Remaining ### Out of migration scope (per plan) - `DBInbound`, `Status`, `AllSetting` legacy classes — flagged as out of scope in `zod-soft-feather.md`. The mainline migration of `models/inbound.ts` / `models/outbound.ts` cannot delete them entirely while `DBInbound.toInbound()` still imports. - The plan accepts this and treats parity via snapshot baselines instead. ### Nice-to-haves — would not block ship - Reality `sid=` multi-value parsing in share-link import (outbound reality only carries a single shortId — this is server-side state) - `fm=` (FinalMask) param in share-link import - VMess link `xmux` nested JSON parsing (currently round-trips at the XHTTP top level; nested xmux object is left empty) - Tighter `.loose()` removal in `schemas/api/inbound.ts`, `schemas/api/client.ts`, `schemas/xray.ts` — gated on Step 6 of the plan (currently held because the codegen tool still emits one or two loose fields the panel writes back) --- ## How to pick up where this left off 1. `git checkout feat/frontend-zod-validation` 2. `cd frontend && npm install && npm run typecheck && npm run test` 3. Open `C:\Users\Hossein Sanaei\.claude\plans\zod-soft-feather.md` — Steps 1–5 are done. Step 6 (tighten `.loose()`) and Step 7 (lint/CI tightening) are partially done. 4. Nothing in this list blocks ship. The mainline migration goal (replace class-based models with Zod schemas + Pattern A forms) is done; remaining work is incremental polish.