diff --git a/generic.md b/generic.md index 89cdff6..0137412 100644 --- a/generic.md +++ b/generic.md @@ -22,7 +22,7 @@ Projects are Rust cargo workspaces. The repository root contains: │ ├── -api/ # binary: REST / JSON / WebSocket daemon │ ├── -worker/ # binary: long-running processor / queue consumer │ └── -cli/ # binary: operator / admin CLI -├── web/ # Vite + React + SWC + TS frontend (when applicable) +├── / # Vite + React + SWC + TS frontend(s) — see §4 ├── asset/ # deployment artifacts (see §6) ├── script/ # deploy.sh and related operational scripts └── README.md @@ -130,10 +130,19 @@ API and worker binaries are managed by systemd unit files shipped from `asset/sy ## 4. Frontends ### Web (default) -Vite + React + SWC + TypeScript, in `/web/`: +Vite + React + SWC + TypeScript. The frontend lives in a top-level directory named for what it *is* to the user, not a generic `web/`. Common names: + +- `web/` — the primary public-facing app, when there's only one frontend +- `ui/` — equivalent, when the project prefers this naming +- `dashboard/` — a user-facing dashboard UI +- `admin/` — an operator/admin console distinct from the public UI + +A project may have more than one of these (e.g., both `dashboard/` and `admin/`). Each is an independent Vite app with its own `package.json`, built and deployed separately, typically served from its own nginx `server_name` or path prefix. Pick names that describe the audience or purpose; don't invent a generic wrapper directory to hold them. + +Whatever the directory is called, the internal structure is the same: ``` -web/ +/ ├── package.json ├── vite.config.ts ├── tsconfig.json @@ -150,6 +159,7 @@ web/ - Build output is static. Deployed to an nginx CDN endpoint — no Node.js in production. - API base URL is configured at build time (Vite `import.meta.env.VITE_API_BASE_URL`) and stamped per environment during deploy. - Prefer React Query or equivalent for server state. Keep business logic server-side; the frontend is a rendering and interaction layer. +- When a project has multiple frontends, they may share types via a local package (e.g., `packages/shared-types/`) or via generated TypeScript bindings from the Rust `entities` crate. Don't duplicate API clients across frontends — factor the shared bits out. ### Web (Rust framework exception) Use a Rust web framework (Axum + templating, or a fullstack framework) **only when** the deployment model requires a single self-contained binary with no external web server — e.g., distributed orchestration nodes that each serve their own UI over TLS. The Cichlid pattern. Default is still Vite + nginx. @@ -516,7 +526,7 @@ When scaffolding or extending a project: 5. Any new deployable component gets an entry in `asset/manifest.yml`, a systemd unit in `asset/systemd/`, a sysusers drop-in, a firewalld service XML, and any required SELinux assets — in the same change. 6. Config templates go in `asset/config/` with `{{PLACEHOLDER}}` secrets. Never commit a rendered config. 7. Postgres connections are mTLS, passwordless. If writing connection code that accepts a password, stop and ask. -8. Frontend is Vite + React + SWC + TS, served as static assets from nginx. Rust web frameworks require a stated reason. +8. Frontends are Vite + React + SWC + TS, served as static assets from nginx. Name the directory after its audience (`web/`, `ui/`, `dashboard/`, `admin/`) — `web/` is not a mandated convention. Rust web frameworks require a stated reason. 9. Services run as dedicated non-root users with hardened systemd units per §8. Root requires explicit justification. 10. Every listening port gets a named firewalld service per §9. No bare `--add-port` calls. 11. SELinux stays enforcing. Work with the default policy first; ship a custom module only when necessary (§10). Never suggest `setenforce 0`.