Skip to content
bitzorcas
中EN

Concept

Frontend overview

The BitzOrcas frontend is a Yarn 4 monorepo. The web admin app is React 19 + Vite; the mobile shell is Taro 4 + Capacitor. Both reach the .NET backend through one typed contract.

Last updated

The BitzOrcas frontend lives under frontend/ in the backend monorepo, as a peer of the .NET src/ tree. It was imported with git subtree add from the standalone bitzeditor repository, so it ships with its own package.json, yarn.lock, and a vendored Yarn 4 release. The two sides never share source — they are connected only by a typed contract and a documented authentication flow.

1. Where the frontend sits

ConcernBackend (.NET)Frontend (TypeScript)
Repository locationsrc/, tests/, src/Hostsfrontend/apps, frontend/packages
Package managerNuGet + Central Package ManagementYarn 4.17.0 (packageManager field; .yarn/releases/yarn-4.17.0.cjs vendored)
Runtime.NET 10 (Api, JobHost, AppHost)Node; browser / native shells
Contract surfaceemits artifacts/openapi/openapi-v1.json@bitz/platform-sdk consumes it
Auth truthJWT + WebRefreshCookieServiceaccess token in memory; refresh token in an HttpOnly cookie

The frontend must not live inside src/. It is a peer subtree with an independent toolchain, exactly so a backend build never depends on Node and a frontend build never depends on the .NET SDK.

2. Two stacks, one monorepo

There are two apps, and they are not the same stack. The web admin and the mobile shell share a Yarn workspace but little else:

not used by

frontend/ (Yarn 4 workspaces: apps/* + packages/*)

apps/app @bitz/app

apps/app-mobile @bitz/app-mobile

React 19 · Vite 8 · RR7 · Jotai · TanStack Query 5

platform-sdk · widgets · components · editor · i18n

Taro 4 · React 18 · Capacitor 8 · Jotai

AppStackReactBundlerBackend bridge
apps/app (web admin)Vite SPA, RR7, Jotai, TanStack Query 5, Tailwind v419Vite 8 + rolldown, Babel 8 (dev-only Locator)@bitz/platform-sdk + @bitz/widgets
apps/app-mobile (mobile shell)Taro 4 multi-target (H5 / WeChat / Harmony), Capacitor 8 native (Android / iOS)18Taro’s Vite 4 runner, Babel 7none — independent Jotai store; no platform-sdk

The version split is deliberate: Taro 4 currently targets React 18, and its build toolchain pins Vite 4 / Babel 7, which is why the mobile workspace is resolutions-locked to those versions. See Mobile shell for the full picture.

3. The web app’s request path

.NET Api Hostfetch · credentials: includeAuthSessionrequest pipeline (singleton)PlatformProvider (usePlatform)Page component.NET Api Hostfetch · credentials: includeAuthSessionrequest pipeline (singleton)PlatformProvider (usePlatform)Page componentalt[401 and not already refreshed]api.searchUsers(query)request('/api/users', opts)inject base URL · Authorization · X-Client-PlatformAbortSignal.timeout(30s)QUERY /api/users (POST /_query if unsupported)200 or RFC 9457 ProblemDetailsresponserefreshOnce()new access tokenretry oncePlatformResult<UserPage>{ ok: true, data } | { ok: false, error }

The whole app shares one request-pipeline instance (created in PlatformProvider via useMemo) and one AuthSession. Pages never construct a client; they call usePlatform().api.<method>(...). See the platform contract layer for SDK details and the QUERY protocol for paged, list, and complex read transport.

4. Tech stack (web app)

LayerChoiceNotes
LanguageTypeScript 6+strict; each workspace tsc --noEmit
Package managerYarn 4.17.0nodeLinker: node-modules; .yarn/releases/yarn-4.17.0.cjs vendored; registry registry.npmmirror.com
UI frameworkReact 19+@bitz/app
BundlerVite 8 + rolldowndev proxy keeps the browser same-origin to avoid CORS
RouterReact Router 7react-router-dom; lazy routes via import.meta.glob; route-level RouteGuard
Client stateJotaiatoms; URL state via nuqs
Server stateTanStack React Query 5the only data-fetching surface in the web app
StylingTailwind CSS v4@tailwindcss/vite
UI primitivesshadcn patterns (Radix / Base UI)@bitz/components — a shadcn primitive barrel (dozens of UI re-exports)
Rich textTiptap 3@bitz/editor — a useBitzEditor wrapper over StarterKit
Iconsunplugin-icons + @iconify-json/lucideauto-imported
Build helpersunplugin-auto-import, unplugin-svgr
i18n@bitz/i18nContext + type-safe t(); currently zh-CN only, multi-locale deferred
PWAvite-plugin-pwaregisterType: prompt; manifest FD WORK
QualityESLint + typescript-eslint, Prettier, Husky, lint-staged, commitlintConventional Commits; Chinese subjects allowed

5. Workspace layout

frontend/
├── apps/
│ ├── app/ @bitz/app — Web admin SPA (React 19 + Vite 8)
│ └── app-mobile/ @bitz/app-mobile — Taro 4 + Capacitor 8 (independent stack)
└── packages/
├── platform-sdk/ @bitz/platform-sdk — deep module: request pipeline, auth session, gates, contract types
├── widgets/ @bitz/widgets — composite business widgets (AppShell, ServerDataTable, FormField …)
├── components/ @bitz/components — shadcn primitive barrel (forms, tables, overlays, …) + `cn` helper
├── editor/ @bitz/editor — Tiptap `useBitzEditor` wrapper
├── i18n/ @bitz/i18n — Context + type-safe t() (zh-CN bundle)
├── materials/ @bitz/materials — Design System 1.2 token source, generator, and runtime
├── hooks/ @bitz/hooks — `useDebounce` (scaffold-stage)
├── utils/ @bitz/utils — `slugify`, `cx`, `formatDate`
└── scripts/ @bitz/scripts — scaffold banner helper

apps/* and packages/* are the two Yarn workspace globs. A page imports from package barrels (@bitz/widgets, @bitz/platform-sdk) and never reaches into a package’s deep file paths. materials now owns the token source and generation gate; hooks, scripts, and editor remain relatively thin packages.

6. Contract with the backend

The web app speaks to the backend through one channel, not ad-hoc HTTP:

  1. The backend emits an OpenAPI artifact at artifacts/openapi/openapi-v1.json via scripts/build/export-openapi.sh.
  2. @bitz/platform-sdk consumes the artifact; generate-client refreshes src/client/generated.d.ts, and module contract files only bridge generated schemas.
  3. All runtime calls flow through the singleton request pipeline — which injects base URL, Authorization: Bearer, and X-Client-Platform headers, sends credentials: 'include' so the HttpOnly refresh cookie travels automatically, and parses RFC 9457 ProblemDetails into a typed AppError.
  4. Drift between artifact and shipped API is guarded by scripts/build/check-openapi-drift.sh.

Authentication follows a fixed contract: login returns an access token in the JSON body and a refresh token in an HttpOnly cookie the browser carries automatically. Refresh reads the cookie; logout revokes the refresh token server-side and clears the cookie.

7. Red lines

These are implemented architectural invariants.

#Red lineStatus
1Generated client is never hand-editedCurrent. Change the backend contract, then export and regenerate.
2Frontend permission checks are UX onlyCurrent. The backend authorization pipeline is the single permission truth.
3No refresh token in localStorageCurrent. Web refresh is HttpOnly-cookie only; access token is a module-private field.
4No permission fact in localStorageCurrent. TenantId / UserId / permissions come from /api/auth/me or JWT claims.
5No hand-written backend DTOCurrent. Module contracts may only alias schemas from generated.d.ts.
6No scattered fetch in pagesCurrent. All HTTP goes through usePlatform().api.
7One request pipeline instanceCurrent. PlatformProvider memoizes one client for the whole app.

8. Getting started

Terminal window
# from the backend monorepo root
cd frontend
# strict install against the lockfile
yarn install --immutable
# web admin dev server (Vite, port 6800, dev proxy → Api Host 6881)
yarn dev
# mobile H5 shell (separate stack; see Mobile shell page)
yarn dev:mobile

The dev proxy means the browser sees same-origin /api/* on 6800, so there is no CORS in development. When VITE_API_BASE_URL is set instead, the frontend calls the backend directly and the backend must permit the origin.

9. Learning routes

GoalReading order
Understand the architectureThis page → Architecture & red lines → Platform contract layer
Understand list requestsQUERY protocol → Platform contract layer
Understand sign-in and account securityThis page → Identity frontend → Platform contract layer
Build a web admin screenPlatform contract layer → Web admin app
Ship a mobile buildMobile shell → Toolchain & contributor flow
Contribute a changeToolchain & contributor flow

10. Source review

From the backend monorepo root:

Terminal window
# Confirm the frontend subtree, the pinned Yarn runtime, and the two-app layout.
rg -n "packageManager" frontend/package.json
rg -n '"react"' frontend/apps/app/package.json frontend/apps/app-mobile/package.json
ls frontend/apps frontend/packages

A source change to the frontend contract, package layout, or red lines forces this page to be reviewed rather than silently going stale.

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%