update ui update and fix layout issue
This commit is contained in:
120
ARCHITECTURE.md
Normal file
120
ARCHITECTURE.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Architecture
|
||||
|
||||
How this codebase is organised, and the rules that keep it that way. Read this
|
||||
before adding a feature; it should take about five minutes.
|
||||
|
||||
## The layers
|
||||
|
||||
Data flows in one direction, and each layer is allowed to know only the one
|
||||
below it:
|
||||
|
||||
```
|
||||
component / page renders state, owns no rules
|
||||
↓
|
||||
features/<f>/hooks binds a service or repository to React
|
||||
↓
|
||||
features/<f>/services domain rules: validation, interpretation, defaults
|
||||
↓
|
||||
features/<f>/repositories TRANSPORT ONLY — the single place that knows a URL
|
||||
↓
|
||||
app/api/<f>/route.ts HTTP boundary: authorises, parses, responds
|
||||
↓
|
||||
features/<f>/mock/*.mock fixtures, server-side only
|
||||
```
|
||||
|
||||
**Connecting a real backend touches the repository layer and nothing else.**
|
||||
Point `NEXT_PUBLIC_API_BASE` at a host, or rewrite the four lines in a
|
||||
repository, and every component above it is untouched. That is the property the
|
||||
whole structure exists to protect, so:
|
||||
|
||||
- **Never** `import` from a `mock/` module in a component, hook or page. Server
|
||||
Components read through a `*ServerRepository`; clients go over HTTP.
|
||||
- **Never** call `fetch` in a component. `shared/services/httpClient` is the
|
||||
only module that does.
|
||||
- A repository never interprets a response, and a service never builds a URL.
|
||||
|
||||
## Where things live
|
||||
|
||||
```
|
||||
src/
|
||||
app/ routing only — thin files, no logic
|
||||
(public)/login unauthenticated routes → PublicLayout → GuestGuard
|
||||
(workspace)/… authenticated routes → ProtectedLayout → AuthGuard
|
||||
api/ route handlers
|
||||
proxy.ts the server-side auth gate (Next 16 renamed middleware → proxy)
|
||||
features/<feature>/
|
||||
components/ UI for this feature only
|
||||
hooks/ data access + view state
|
||||
services/ domain rules, framework-free, unit-testable
|
||||
repositories/ URLs and verbs
|
||||
types/ the wire contract for this feature
|
||||
mock/ fixtures (server-side)
|
||||
utils/ · config/ · guards/ · providers/ as needed
|
||||
shared/
|
||||
components/ brand · charts · patterns · primitives · scope · data · motion
|
||||
hooks/ useResource · useScope · useBreakpoint · usePersistentFlag
|
||||
layouts/ ProtectedLayout · PublicLayout · workspace shell
|
||||
providers/ app-wide state (workspace scope)
|
||||
services/ httpClient (transport) · apiRoute (handler helpers)
|
||||
types/ the response envelope
|
||||
mock/ seeded RNG shared by every fixture
|
||||
theme/ design tokens; edit loyalyTheme.ts, run `npm run theme:build`
|
||||
```
|
||||
|
||||
A feature owns everything about itself. If two features need the same thing, it
|
||||
moves to `shared/` — it does not get imported across features, with one
|
||||
deliberate exception: `features/dashboard/types` holds the analytics primitives
|
||||
(`TimePoint`, `ActivityEvent`) that LYTs and Stores genuinely share, because
|
||||
duplicating them would let two modules disagree about the same wire shape.
|
||||
|
||||
## Authentication
|
||||
|
||||
Three independent gates, in order of authority:
|
||||
|
||||
1. **`src/proxy.ts`** — runs before any protected route renders. No valid
|
||||
session cookie, no page. API paths get a 401 JSON; pages get a redirect to
|
||||
`/login?next=…`. This is the one that matters.
|
||||
2. **`requireApiSession()`** — every data route handler calls it. Next's own
|
||||
docs warn that proxy coverage can be silently lost by a matcher change or a
|
||||
route move, so the check is repeated where the data is.
|
||||
3. **`AuthGuard` / `GuestGuard`** — client-side. Covers what the server never
|
||||
sees: a session expiring in an open tab, a logout in another tab, a
|
||||
client-side navigation. **Not a security boundary.**
|
||||
|
||||
The session is a signed httpOnly cookie (`features/auth/services/sessionToken`),
|
||||
so the browser cannot read or forge it. `SessionProvider` holds only what to
|
||||
draw, never what to permit — it asks `GET /api/auth/session` and believes the
|
||||
answer.
|
||||
|
||||
Swapping the mock for a real identity provider means changing
|
||||
`verifyCredentials` in `features/auth/mock/users.mock.ts` and the three URLs in
|
||||
`authRepository`. Nothing else, including the login form, is aware.
|
||||
|
||||
## Conventions
|
||||
|
||||
- **No `any`.** The codebase has zero, and `tsc --noEmit` is expected to pass
|
||||
before every commit.
|
||||
- **Components stay under ~300 lines.** When one grows past that, the split is
|
||||
usually behaviour-into-a-hook, not markup-into-more-markup.
|
||||
- **Names say what the thing is**: `DashboardKpiCard`, `StaffAttendanceTable`.
|
||||
Never `Card.tsx`, `utils.ts`, `NewFile.tsx`.
|
||||
- **Styling comes from the design system**, in this order: component props →
|
||||
token-backed Tailwind utilities → a theme override in `loyalyTheme.ts`. There
|
||||
are no per-feature CSS files; see the note below.
|
||||
|
||||
### On CSS
|
||||
|
||||
The brief that produced this refactor asked for per-feature stylesheets
|
||||
(`dashboard/styles/kpi.css`, and so on) so that changing one module cannot
|
||||
affect another. That isolation is already total here, by construction rather
|
||||
than by convention: styling lives in component props and utility classes scoped
|
||||
to the element they are written on, so there is no selector in the codebase
|
||||
that *can* reach another feature. Adding stylesheets would introduce global
|
||||
selectors — the one mechanism that can leak — and they would have to fight
|
||||
StyleX's `@layer astryx-base` for the cascade. Shared visual decisions belong in
|
||||
`theme/loyalyTheme.ts`, which is the single place a change is meant to be
|
||||
system-wide.
|
||||
|
||||
If a future component genuinely needs CSS that props and utilities cannot
|
||||
express, `astryx swizzle <Component>` ejects the source for that one component
|
||||
rather than opening a stylesheet the whole app can be edited through.
|
||||
Reference in New Issue
Block a user