Unify workspace dropdowns; Update layouts (#458)

Unified Account and Workspace drop-downs; Layout updates, now header lives within the PageBody component; Sidebars now use floating variant
This commit is contained in:
Giancarlo Buomprisco
2026-03-11 14:45:42 +08:00
committed by GitHub
parent ca585e09be
commit 4bc8448a1d
530 changed files with 14398 additions and 11198 deletions

View File

@@ -9,7 +9,9 @@ You are an expert at building pure, testable services that are decoupled from th
## North Star
**Every service is decoupled from its interface (I/O).** A service takes plain data in, does work, and returns plain data out. It has no knowledge of whether it was called from an MCP tool, a server action, a CLI command, a route handler, or a test. The caller is a thin adapter that resolves dependencies and delegates.
**Every service is decoupled from its interface (I/O).** A service takes plain data in, does work, and returns plain
data out. It has no knowledge of whether it was called from an MCP tool, a server action, a CLI command, a route
handler, or a test. The caller is a thin adapter that resolves dependencies and delegates.
## Workflow
@@ -21,7 +23,7 @@ Start with the input/output types. These are plain TypeScript — no framework t
```typescript
// _lib/schemas/project.schema.ts
import { z } from 'zod';
import * as z from 'zod';
export const CreateProjectSchema = z.object({
name: z.string().min(1),
@@ -40,7 +42,8 @@ export interface Project {
### Step 2: Build the Service
The service receives all dependencies through its constructor. It never imports framework-specific modules (`getSupabaseServerClient`, `getLogger`, `revalidatePath`, etc.).
The service receives all dependencies through its constructor. It never imports framework-specific modules (
`getSupabaseServerClient`, `getLogger`, `revalidatePath`, etc.).
```typescript
// _lib/server/project.service.ts
@@ -95,7 +98,8 @@ class ProjectService {
### Step 3: Write Thin Adapters
Each interface is a thin adapter — it resolves dependencies, calls the service, and handles interface-specific concerns (revalidation, redirects, MCP formatting, CLI output).
Each interface is a thin adapter — it resolves dependencies, calls the service, and handles interface-specific
concerns (revalidation, redirects, MCP formatting, CLI output).
**Server Action adapter:**
@@ -234,27 +238,32 @@ describe('ProjectService', () => {
## Rules
1. **Services are pure functions over data.** Plain objects/primitives in, plain objects/primitives out. No `Request`/`Response`, no MCP context, no `FormData`.
1. **Services are pure functions over data.** Plain objects/primitives in, plain objects/primitives out. No `Request`/
`Response`, no MCP context, no `FormData`.
2. **Inject dependencies, never import them.** The service receives its database client, storage client, or any I/O capability as a constructor argument. Never call `getSupabaseServerClient()` inside a service.
2. **Inject dependencies, never import them.** The service receives its database client, storage client, or any I/O
capability as a constructor argument. Never call `getSupabaseServerClient()` inside a service.
3. **Adapters are trivial glue.** A server action resolves the client, calls the service, and handles `revalidatePath`. An MCP tool resolves the client, calls the service, and formats the response. No business logic in adapters.
3. **Adapters are trivial glue.** A server action resolves the client, calls the service, and handles `revalidatePath`.
An MCP tool resolves the client, calls the service, and formats the response. No business logic in adapters.
4. **One service, many callers.** If two interfaces do the same thing, they call the same service function. Duplicating logic is a violation.
4. **One service, many callers.** If two interfaces do the same thing, they call the same service function. Duplicating
logic is a violation.
5. **Testable in isolation.** Pass a mock client, assert the output. If you need a running database to test a service, refactor until you don't.
5. **Testable in isolation.** Pass a mock client, assert the output. If you need a running database to test a service,
refactor until you don't.
## What Goes Where
| Concern | Location | Example |
|---------|----------|---------|
| Input validation (Zod) | `_lib/schemas/` | `CreateProjectSchema` |
| Business logic | `_lib/server/*.service.ts` | `ProjectService.create()` |
| Auth check | Adapter (`enhanceAction({ auth: true })`) | Server action wrapper |
| Logging | Adapter | `logger.info()` before/after service call |
| Cache revalidation | Adapter | `revalidatePath()` after mutation |
| Redirect | Adapter | `redirect()` after creation |
| MCP response format | Adapter | Return service result as MCP content |
| Concern | Location | Example |
|------------------------|-------------------------------------------|-------------------------------------------|
| Input validation (Zod) | `_lib/schemas/` | `CreateProjectSchema` |
| Business logic | `_lib/server/*.service.ts` | `ProjectService.create()` |
| Auth check | Adapter (`enhanceAction({ auth: true })`) | Server action wrapper |
| Logging | Adapter | `logger.info()` before/after service call |
| Cache revalidation | Adapter | `revalidatePath()` after mutation |
| Redirect | Adapter | `redirect()` after creation |
| MCP response format | Adapter | Return service result as MCP content |
## File Structure
@@ -305,4 +314,5 @@ const result = await client.from('projects').insert(...).select().single();
## Reference
See `[Examples](examples.md)` for more patterns including services with multiple dependencies, services that compose other services, and testing strategies.
See `[Examples](examples.md)` for more patterns including services with multiple dependencies, services that compose
other services, and testing strategies.