feat: add system configuration platform

This commit is contained in:
2026-07-02 00:03:27 -07:00
parent aed5c6db56
commit e3e7b0d8e0
41 changed files with 5746 additions and 405 deletions

View File

@@ -22,6 +22,8 @@
- Default data file: `.data/teatea.json`.
- Override key: `TEATEA_DATA_DIR` points to the directory containing `teatea.json`.
- `.data/` must stay ignored; it may contain account hashes and session IDs.
- Routes or layouts that read local JSON state or session cookies must opt out of static prerendering with `export const dynamic = "force-dynamic"`.
- API JSON responses backed by local JSON state or session cookies must include `Cache-Control: no-store`.
- API response shape:
```ts
@@ -51,6 +53,7 @@ type ApiResult<T extends Record<string, unknown>> =
- Good: UI submits to Route Handler, Route Handler validates input, calls `requirePermission`, mutates data through `updateData`, writes audit log, returns `ApiResult`.
- Base: Server Component reads data directly via `readData` after session/permission checks.
- Bad: UI or route handler reads `.data/teatea.json` directly, bypasses `requirePermission`, or stores auth state in localStorage.
- Bad: `/app/*`, `/login`, or `/setup` is prerendered as static content and caches a redirect based on build-time empty data.
### 6. Tests Required
@@ -60,6 +63,8 @@ type ApiResult<T extends Record<string, unknown>> =
- Manual or automated integration assertions:
- first setup creates admin and cookie session
- protected app route redirects without cookie
- protected app route renders with a valid cookie after setup and does not return a cached `/setup` redirect
- auth/bootstrap/session API responses include `Cache-Control: no-store`
- CRUD mutation persists across reload/API list
- denied permission returns 403 and writes an audit log
@@ -84,3 +89,28 @@ cookieStore.set({
});
```
#### Wrong
```ts
export default async function AppLayout({ children }: AppLayoutProps) {
const bootstrap = await getBootstrapState();
if (bootstrap.setupRequired) {
redirect("/setup");
}
return children;
}
```
#### Correct
```ts
export const dynamic = "force-dynamic";
export default async function AppLayout({ children }: AppLayoutProps) {
const bootstrap = await getBootstrapState();
if (bootstrap.setupRequired) {
redirect("/setup");
}
return children;
}
```