feat: Sprint 0 — CI/CD pipeline, production Docker, health checks
CI Pipeline (.github/workflows/ci.yml): - 5 jobs: typecheck, lint, test, build, e2e (parallel where possible) - PostgreSQL 16 + Redis 7 service containers for test/e2e - pnpm store, Turborepo, Playwright browser caching - Concurrency groups cancel in-progress runs Production Docker: - Dockerfile.prod: 3-stage build (deps → build → runtime ~150MB) - docker-compose.prod.yml: postgres + redis + app with health checks - .dockerignore for fast builds - next.config.ts: output: "standalone" for minimal runtime Health Check Endpoints: - GET /api/health — liveness probe (200 OK, no deps) - GET /api/ready — readiness probe (postgres + redis connectivity) Documentation: - docs/ci-cd-manual.md — full pipeline manual with troubleshooting - plan.md — Product Owner strategic plan (bottlenecks, growth, automation) Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
# Dependencies (reinstalled in container)
|
||||
node_modules
|
||||
**/node_modules
|
||||
|
||||
# Build outputs
|
||||
.next
|
||||
**/dist
|
||||
**/.turbo
|
||||
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Dev tooling
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Environment files (injected at runtime)
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Test artifacts
|
||||
coverage
|
||||
**/coverage
|
||||
e2e-results
|
||||
playwright-report
|
||||
|
||||
# Docker files (avoid recursive context)
|
||||
Dockerfile*
|
||||
docker-compose*
|
||||
|
||||
# Documentation
|
||||
docs
|
||||
*.md
|
||||
!packages/*/README.md
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,265 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NODE_VERSION: "20"
|
||||
PNPM_VERSION: "9.14.2"
|
||||
|
||||
jobs:
|
||||
# ──────────────────────────────────────────────
|
||||
# Typecheck — ~40s, no services needed
|
||||
# ──────────────────────────────────────────────
|
||||
typecheck:
|
||||
name: Typecheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Generate Prisma client
|
||||
run: pnpm --filter @planarchy/db exec prisma generate
|
||||
|
||||
- name: Cache Turborepo
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .turbo
|
||||
key: turbo-typecheck-${{ github.sha }}
|
||||
restore-keys: turbo-typecheck-
|
||||
|
||||
- name: Run typecheck
|
||||
run: pnpm --filter @planarchy/web exec tsc --noEmit
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Lint — ~20s, no services needed
|
||||
# ──────────────────────────────────────────────
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Generate Prisma client
|
||||
run: pnpm --filter @planarchy/db exec prisma generate
|
||||
|
||||
- name: Cache Turborepo
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .turbo
|
||||
key: turbo-lint-${{ github.sha }}
|
||||
restore-keys: turbo-lint-
|
||||
|
||||
- name: Run lint
|
||||
run: pnpm lint
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Unit tests — needs PostgreSQL + Redis
|
||||
# ──────────────────────────────────────────────
|
||||
test:
|
||||
name: Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
env:
|
||||
POSTGRES_DB: planarchy_test
|
||||
POSTGRES_USER: planarchy
|
||||
POSTGRES_PASSWORD: planarchy_test
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd="pg_isready -U planarchy -d planarchy_test"
|
||||
--health-interval=10s
|
||||
--health-timeout=5s
|
||||
--health-retries=5
|
||||
redis:
|
||||
image: redis:7
|
||||
ports:
|
||||
- 6379:6379
|
||||
options: >-
|
||||
--health-cmd="redis-cli ping"
|
||||
--health-interval=10s
|
||||
--health-timeout=5s
|
||||
--health-retries=5
|
||||
env:
|
||||
DATABASE_URL: postgresql://planarchy:planarchy_test@localhost:5432/planarchy_test
|
||||
REDIS_URL: redis://localhost:6379
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Generate Prisma client
|
||||
run: pnpm --filter @planarchy/db exec prisma generate
|
||||
|
||||
- name: Run unit tests
|
||||
run: pnpm test:unit
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Build — depends on typecheck passing
|
||||
# ──────────────────────────────────────────────
|
||||
build:
|
||||
name: Build
|
||||
needs: [typecheck]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DATABASE_URL: postgresql://placeholder:placeholder@localhost:5432/placeholder
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Generate Prisma client
|
||||
run: pnpm --filter @planarchy/db exec prisma generate
|
||||
|
||||
- name: Cache Turborepo
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .turbo
|
||||
key: turbo-build-${{ github.sha }}
|
||||
restore-keys: turbo-build-
|
||||
|
||||
- name: Cache Next.js build
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: apps/web/.next/cache
|
||||
key: nextjs-${{ hashFiles('pnpm-lock.yaml') }}-${{ github.sha }}
|
||||
restore-keys: nextjs-${{ hashFiles('pnpm-lock.yaml') }}-
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter @planarchy/web exec next build
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# E2E — depends on build, needs PostgreSQL + Redis
|
||||
# ──────────────────────────────────────────────
|
||||
e2e:
|
||||
name: E2E Tests
|
||||
needs: [build]
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
env:
|
||||
POSTGRES_DB: planarchy_test
|
||||
POSTGRES_USER: planarchy
|
||||
POSTGRES_PASSWORD: planarchy_test
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd="pg_isready -U planarchy -d planarchy_test"
|
||||
--health-interval=10s
|
||||
--health-timeout=5s
|
||||
--health-retries=5
|
||||
redis:
|
||||
image: redis:7
|
||||
ports:
|
||||
- 6379:6379
|
||||
options: >-
|
||||
--health-cmd="redis-cli ping"
|
||||
--health-interval=10s
|
||||
--health-timeout=5s
|
||||
--health-retries=5
|
||||
env:
|
||||
DATABASE_URL: postgresql://planarchy:planarchy_test@localhost:5432/planarchy_test
|
||||
REDIS_URL: redis://localhost:6379
|
||||
PORT: 3100
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Generate Prisma client
|
||||
run: pnpm --filter @planarchy/db exec prisma generate
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
id: playwright-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: playwright-${{ hashFiles('apps/web/package.json') }}
|
||||
restore-keys: playwright-
|
||||
|
||||
- name: Install Playwright browsers
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
run: pnpm --filter @planarchy/web exec playwright install --with-deps chromium
|
||||
|
||||
- name: Install Playwright system deps
|
||||
if: steps.playwright-cache.outputs.cache-hit == 'true'
|
||||
run: pnpm --filter @planarchy/web exec playwright install-deps chromium
|
||||
|
||||
- name: Push DB schema & seed
|
||||
run: |
|
||||
pnpm db:push
|
||||
pnpm db:seed
|
||||
|
||||
- name: Run E2E tests
|
||||
run: pnpm test:e2e
|
||||
|
||||
- name: Upload Playwright report
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: playwright-report
|
||||
path: apps/web/playwright-report/
|
||||
retention-days: 14
|
||||
@@ -0,0 +1,80 @@
|
||||
# ============================================================
|
||||
# Stage 1: Install dependencies
|
||||
# ============================================================
|
||||
FROM node:20-bookworm-slim AS deps
|
||||
|
||||
RUN apt-get update -y && apt-get install -y openssl && rm -rf /var/lib/apt/lists/*
|
||||
RUN npm install -g pnpm@9.14.2
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy workspace manifests first for better layer caching
|
||||
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./
|
||||
COPY tooling/ ./tooling/
|
||||
COPY packages/shared/package.json ./packages/shared/
|
||||
COPY packages/db/package.json ./packages/db/
|
||||
COPY packages/engine/package.json ./packages/engine/
|
||||
COPY packages/staffing/package.json ./packages/staffing/
|
||||
COPY packages/application/package.json ./packages/application/
|
||||
COPY packages/api/package.json ./packages/api/
|
||||
COPY packages/ui/package.json ./packages/ui/
|
||||
COPY apps/web/package.json ./apps/web/
|
||||
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# ============================================================
|
||||
# Stage 2: Build the application
|
||||
# ============================================================
|
||||
FROM node:20-bookworm-slim AS builder
|
||||
|
||||
RUN apt-get update -y && apt-get install -y openssl && rm -rf /var/lib/apt/lists/*
|
||||
RUN npm install -g pnpm@9.14.2
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy installed dependencies from stage 1
|
||||
COPY --from=deps /app/ ./
|
||||
|
||||
# Copy all source code
|
||||
COPY . .
|
||||
|
||||
# Generate Prisma client
|
||||
RUN pnpm --filter @planarchy/db db:generate
|
||||
|
||||
# Build the Next.js application
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
ENV NODE_ENV=production
|
||||
RUN pnpm --filter @planarchy/web build
|
||||
|
||||
# ============================================================
|
||||
# Stage 3: Production runtime
|
||||
# ============================================================
|
||||
FROM node:20-bookworm-slim AS runner
|
||||
|
||||
RUN apt-get update -y && apt-get install -y openssl curl && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
ENV HOSTNAME=0.0.0.0
|
||||
ENV PORT=3000
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs && \
|
||||
adduser --system --uid 1001 nextjs
|
||||
|
||||
# Copy the standalone output (includes server.js and node_modules)
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
|
||||
|
||||
# Copy static assets and public files
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
||||
CMD curl -f http://localhost:3000/api/health || exit 1
|
||||
|
||||
CMD ["node", "apps/web/server.js"]
|
||||
@@ -2,6 +2,7 @@ import path from "path";
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: "standalone",
|
||||
experimental: {
|
||||
optimizePackageImports: ["recharts", "date-fns"],
|
||||
},
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const runtime = "nodejs";
|
||||
|
||||
export function GET() {
|
||||
return NextResponse.json({
|
||||
status: "ok",
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@planarchy/db";
|
||||
import { createConnection } from "net";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const runtime = "nodejs";
|
||||
|
||||
const REDIS_URL = process.env["REDIS_URL"] ?? "redis://localhost:6380";
|
||||
|
||||
async function checkPostgres(): Promise<"ok" | "error"> {
|
||||
try {
|
||||
await prisma.$queryRaw`SELECT 1`;
|
||||
return "ok";
|
||||
} catch {
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight Redis PING check using a raw TCP socket.
|
||||
* Avoids importing ioredis (which is only a dependency of @planarchy/api).
|
||||
*/
|
||||
async function checkRedis(): Promise<"ok" | "error"> {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
const url = new URL(REDIS_URL);
|
||||
const host = url.hostname || "localhost";
|
||||
const port = parseInt(url.port || "6379", 10);
|
||||
const timeout = 3000;
|
||||
|
||||
const socket = createConnection({ host, port }, () => {
|
||||
// Send Redis PING command using RESP protocol
|
||||
socket.write("*1\r\n$4\r\nPING\r\n");
|
||||
});
|
||||
|
||||
socket.setTimeout(timeout);
|
||||
|
||||
socket.on("data", (data) => {
|
||||
const response = data.toString();
|
||||
socket.destroy();
|
||||
// Redis responds with +PONG\r\n
|
||||
resolve(response.includes("PONG") ? "ok" : "error");
|
||||
});
|
||||
|
||||
socket.on("timeout", () => {
|
||||
socket.destroy();
|
||||
resolve("error");
|
||||
});
|
||||
|
||||
socket.on("error", () => {
|
||||
socket.destroy();
|
||||
resolve("error");
|
||||
});
|
||||
} catch {
|
||||
resolve("error");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const [postgres, redis] = await Promise.all([
|
||||
checkPostgres(),
|
||||
checkRedis(),
|
||||
]);
|
||||
|
||||
const allHealthy = postgres === "ok" && redis === "ok";
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: allHealthy ? "ready" : "not_ready",
|
||||
postgres,
|
||||
redis,
|
||||
},
|
||||
{ status: allHealthy ? 200 : 503 },
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
POSTGRES_DB: planarchy
|
||||
POSTGRES_USER: planarchy
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-changeme}
|
||||
volumes:
|
||||
- planarchy_prod_pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U planarchy"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
shm_size: "256mb"
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "6379:6379"
|
||||
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
|
||||
volumes:
|
||||
- planarchy_prod_redis:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 5s
|
||||
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.prod
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000"
|
||||
env_file:
|
||||
- .env.production
|
||||
environment:
|
||||
DATABASE_URL: postgresql://planarchy:${POSTGRES_PASSWORD:-changeme}@postgres:5432/planarchy
|
||||
REDIS_URL: redis://redis:6379
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
|
||||
volumes:
|
||||
planarchy_prod_pgdata:
|
||||
planarchy_prod_redis:
|
||||
@@ -0,0 +1,316 @@
|
||||
# Planarchy CI/CD Manual
|
||||
|
||||
## Overview
|
||||
|
||||
Planarchy uses GitHub Actions for continuous integration and Docker for deployment. This document covers the full pipeline from code push to production.
|
||||
|
||||
---
|
||||
|
||||
## 1. CI Pipeline (Automatic on every PR)
|
||||
|
||||
### What triggers it
|
||||
|
||||
| Event | Trigger |
|
||||
|-------|---------|
|
||||
| Pull request to `main` | All CI jobs run |
|
||||
| Push to `main` | All CI jobs run |
|
||||
|
||||
### Jobs and their purpose
|
||||
|
||||
```
|
||||
PR opened / pushed
|
||||
│
|
||||
├──→ typecheck (tsc --noEmit, ~40s)
|
||||
├──→ lint (ESLint via Turborepo, ~20s)
|
||||
├──→ test (Vitest unit tests, ~60s, needs PostgreSQL + Redis)
|
||||
│
|
||||
└──→ build (next build, ~90s, runs after typecheck)
|
||||
│
|
||||
└──→ e2e (Playwright, ~3-5min, runs after build)
|
||||
```
|
||||
|
||||
**typecheck, lint, and test run in parallel** for speed. Build waits for typecheck. E2E waits for build.
|
||||
|
||||
### What each job checks
|
||||
|
||||
| Job | Command | What it catches |
|
||||
|-----|---------|----------------|
|
||||
| **typecheck** | `pnpm --filter @planarchy/web exec tsc --noEmit` | Type errors across the full web app |
|
||||
| **lint** | `pnpm lint` | Code style violations, unused imports, etc. |
|
||||
| **test** | `pnpm test:unit` | Unit test failures in engine, staffing, API, shared |
|
||||
| **build** | `pnpm --filter @planarchy/web exec next build` | SSR errors, dynamic import issues, bundle problems |
|
||||
| **e2e** | `pnpm test:e2e` | End-to-end user flow regressions |
|
||||
|
||||
### Required status checks
|
||||
|
||||
Before merging a PR, **all 5 jobs must pass**. Configure this in GitHub Settings > Branches > Branch protection rules > Require status checks.
|
||||
|
||||
### Caching
|
||||
|
||||
The pipeline caches these artifacts to speed up subsequent runs:
|
||||
|
||||
| Cache | Key | Saves |
|
||||
|-------|-----|-------|
|
||||
| pnpm store | `pnpm-lock.yaml` hash | ~30s install time |
|
||||
| Turborepo | `.turbo` directory | ~60s on unchanged packages |
|
||||
| Playwright browsers | Playwright version | ~45s browser download |
|
||||
|
||||
---
|
||||
|
||||
## 2. Local Development Quality Gates
|
||||
|
||||
Run these before pushing to catch issues early:
|
||||
|
||||
```bash
|
||||
# Quick check (< 2 min)
|
||||
pnpm --filter @planarchy/web exec tsc --noEmit && pnpm lint
|
||||
|
||||
# Full check (< 3 min)
|
||||
pnpm test:unit
|
||||
|
||||
# Full check including build (< 5 min)
|
||||
pnpm --filter @planarchy/web exec next build
|
||||
```
|
||||
|
||||
### Pre-commit hook (optional)
|
||||
|
||||
You can add a Git pre-commit hook to run the quick check automatically:
|
||||
|
||||
```bash
|
||||
# .husky/pre-commit
|
||||
pnpm --filter @planarchy/web exec tsc --noEmit
|
||||
pnpm lint
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Health Check Endpoints
|
||||
|
||||
Two endpoints are available for monitoring:
|
||||
|
||||
### GET `/api/health` — Liveness Probe
|
||||
|
||||
Returns 200 if the Node.js process is running. No external dependencies checked.
|
||||
|
||||
```json
|
||||
{ "status": "ok", "timestamp": "2026-03-19T10:00:00.000Z" }
|
||||
```
|
||||
|
||||
**Use for:** Kubernetes/Docker liveness probe, uptime monitoring.
|
||||
|
||||
### GET `/api/ready` — Readiness Probe
|
||||
|
||||
Checks PostgreSQL and Redis connectivity. Returns 200 if all services are reachable, 503 if not.
|
||||
|
||||
```json
|
||||
// Healthy
|
||||
{ "status": "ready", "postgres": "ok", "redis": "ok" }
|
||||
|
||||
// Unhealthy
|
||||
{ "status": "not_ready", "postgres": "ok", "redis": "error" }
|
||||
```
|
||||
|
||||
**Use for:** Kubernetes/Docker readiness probe, load balancer health checks, nginx upstream checks.
|
||||
|
||||
---
|
||||
|
||||
## 4. Production Docker Build
|
||||
|
||||
### Building the production image
|
||||
|
||||
```bash
|
||||
# Build the image
|
||||
docker build -f Dockerfile.prod -t planarchy:latest .
|
||||
|
||||
# Test it locally
|
||||
docker compose -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
### Image details
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Base | `node:20-bookworm-slim` |
|
||||
| Size | ~150-200 MB (vs ~1.5 GB dev image) |
|
||||
| Output | Next.js standalone mode |
|
||||
| Healthcheck | `curl -f http://localhost:3000/api/health` |
|
||||
| Port | 3000 (internal), mapped to 3100 externally |
|
||||
|
||||
### Environment variables
|
||||
|
||||
The production image requires these environment variables:
|
||||
|
||||
```env
|
||||
# Required
|
||||
DATABASE_URL=postgresql://user:pass@host:5432/planarchy
|
||||
REDIS_URL=redis://host:6379
|
||||
NEXTAUTH_URL=https://planarchy.your-domain.com
|
||||
NEXTAUTH_SECRET=<random-32-char-string>
|
||||
|
||||
# Optional
|
||||
SENTRY_DSN=https://xxx@sentry.io/xxx
|
||||
SMTP_HOST=smtp.example.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=notifications@example.com
|
||||
SMTP_PASSWORD=<password>
|
||||
SMTP_FROM=Planarchy <notifications@example.com>
|
||||
```
|
||||
|
||||
Generate a secure `NEXTAUTH_SECRET`:
|
||||
|
||||
```bash
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Deployment
|
||||
|
||||
### docker-compose (simplest)
|
||||
|
||||
```bash
|
||||
# On your server
|
||||
git pull
|
||||
docker compose -f docker-compose.prod.yml up -d --build
|
||||
|
||||
# Run database migrations
|
||||
docker compose -f docker-compose.prod.yml exec app \
|
||||
npx prisma db push --skip-generate
|
||||
|
||||
# Seed initial data (first deployment only)
|
||||
docker compose -f docker-compose.prod.yml exec app \
|
||||
npx prisma db seed
|
||||
```
|
||||
|
||||
### Manual deployment (current setup)
|
||||
|
||||
Since `planarchy.hartmut-noerenberg.com` runs behind nginx:
|
||||
|
||||
```bash
|
||||
# On the server
|
||||
cd /home/hartmut/Documents/Copilot/planarchy
|
||||
git pull origin main
|
||||
pnpm install
|
||||
pnpm --filter @planarchy/db exec prisma generate
|
||||
pnpm --filter @planarchy/web exec next build
|
||||
rm -rf apps/web/.next/cache # clear stale cache
|
||||
|
||||
# Restart the app (systemd, pm2, or manual)
|
||||
fuser -k 3100/tcp 2>/dev/null
|
||||
PORT=3100 pnpm --filter @planarchy/web start &
|
||||
```
|
||||
|
||||
### nginx configuration
|
||||
|
||||
The existing nginx reverse proxy should forward to port 3100:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
server_name planarchy.hartmut-noerenberg.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3100;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# SSE support (keep connection open)
|
||||
proxy_read_timeout 86400s;
|
||||
proxy_buffering off;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Monitoring Setup
|
||||
|
||||
### Sentry (error tracking)
|
||||
|
||||
After creating a Sentry project, add the DSN to `.env.production`:
|
||||
|
||||
```env
|
||||
SENTRY_DSN=https://xxx@sentry.io/xxx
|
||||
```
|
||||
|
||||
Errors are automatically captured by the Sentry integration in Next.js.
|
||||
|
||||
### Uptime monitoring
|
||||
|
||||
Point an external monitor (UptimeRobot, Better Stack, etc.) at:
|
||||
|
||||
```
|
||||
https://planarchy.hartmut-noerenberg.com/api/health
|
||||
```
|
||||
|
||||
Alert if status code != 200 for more than 2 consecutive checks.
|
||||
|
||||
---
|
||||
|
||||
## 7. Troubleshooting
|
||||
|
||||
### CI job fails: "tsc --noEmit"
|
||||
|
||||
TypeScript error in the web app. Run locally:
|
||||
```bash
|
||||
pnpm --filter @planarchy/web exec tsc --noEmit
|
||||
```
|
||||
|
||||
### CI job fails: "test:unit"
|
||||
|
||||
Unit test failure. Run locally:
|
||||
```bash
|
||||
pnpm test:unit
|
||||
```
|
||||
|
||||
### CI job fails: "next build"
|
||||
|
||||
Build error (often `ssr: false` in Server Components, missing exports). Run locally:
|
||||
```bash
|
||||
pnpm --filter @planarchy/web exec next build
|
||||
```
|
||||
|
||||
### CI job fails: "e2e"
|
||||
|
||||
Playwright test failure. Check the HTML report artifact in the GitHub Actions run.
|
||||
|
||||
### Production: 502 Bad Gateway
|
||||
|
||||
The Next.js process isn't running. Check:
|
||||
```bash
|
||||
ss -tlnp | grep 3100 # Is anything listening?
|
||||
tail -50 /tmp/planarchy-dev.log # Check app logs
|
||||
```
|
||||
|
||||
Restart:
|
||||
```bash
|
||||
fuser -k 3100/tcp 2>/dev/null
|
||||
pnpm dev & # or pnpm start for production mode
|
||||
```
|
||||
|
||||
### Production: 500 Internal Server Error
|
||||
|
||||
Usually a stale Prisma client after schema changes:
|
||||
```bash
|
||||
pnpm --filter @planarchy/db exec prisma generate
|
||||
rm -rf apps/web/.next
|
||||
pnpm --filter @planarchy/web exec next build
|
||||
# Restart the server
|
||||
```
|
||||
|
||||
### Database connection issues
|
||||
|
||||
Check the `/api/ready` endpoint:
|
||||
```bash
|
||||
curl -s https://planarchy.hartmut-noerenberg.com/api/ready | jq .
|
||||
```
|
||||
|
||||
If `postgres: "error"`, verify:
|
||||
```bash
|
||||
docker ps | grep postgres # Is container running?
|
||||
psql -h localhost -p 5433 -U planarchy -d planarchy # Can you connect?
|
||||
```
|
||||
@@ -1,258 +1,265 @@
|
||||
# Refactor v2 — Code Optimization, De-duplication & Maintainability
|
||||
# Planarchy — Product Owner Strategic Plan
|
||||
|
||||
## Anforderungsanalyse
|
||||
|
||||
Vollstaendiger Optimierungsdurchlauf des Planarchy-Monorepos. Ziel: Code-Duplikate eliminieren,
|
||||
Performance verbessern, Wartbarkeit erhoehen. Betroffen sind alle Pakete: `apps/web`, `packages/api`,
|
||||
`packages/db`, `packages/shared`.
|
||||
|
||||
Die Analyse identifiziert **7 unabhaengige Arbeitsstroeme (Waves)**, die von Agenten parallel
|
||||
bearbeitet werden koennen, plus eine abschliessende Schema-Migration.
|
||||
> Consolidated analysis from 4 expert agents: Roadmap, API Surface, Frontend UX, and Test Infrastructure.
|
||||
> Date: 2026-03-19
|
||||
|
||||
---
|
||||
|
||||
## Betroffene Pakete & Dateien
|
||||
## Executive Summary
|
||||
|
||||
| Paket | Dateien | Art |
|
||||
|-------|---------|-----|
|
||||
| `apps/web` | `src/lib/format.ts`, 10+ Consumer-Dateien | edit |
|
||||
| `apps/web` | `src/lib/status-styles.ts`, `src/components/timeline/timelineConstants.ts`, 6 Consumer-Dateien | edit |
|
||||
| `apps/web` | `src/hooks/useInvalidatePlanningViews.ts`, 14 Consumer-Dateien | edit |
|
||||
| `apps/web` | `src/components/timeline/renderHelpers.ts` | create |
|
||||
| `apps/web` | `src/components/timeline/TimelineResourcePanel.tsx`, `TimelineProjectPanel.tsx` | edit |
|
||||
| `apps/web` | `src/components/timeline/TimelineView.tsx` | edit |
|
||||
| `apps/web` | `src/hooks/useTimelineDrag.ts` | edit |
|
||||
| `packages/api` | `src/db/helpers.ts`, 7+ Router-Dateien | edit |
|
||||
| `packages/api` | `src/db/selects.ts`, 6+ Router-Dateien | edit |
|
||||
| `packages/db` | `prisma/schema.prisma` | edit |
|
||||
Planarchy has reached **Phase 9** with a mature core: timeline planning, allocation management, estimating, vacation pro, skill matrix, RBAC, and chargeability reporting. The product covers 34 routes, 47 DB models, ~200 tRPC procedures, and 109+ domain components.
|
||||
|
||||
**However, the product has critical gaps preventing production readiness and growth:**
|
||||
|
||||
| Dimension | Score | Verdict |
|
||||
|-----------|-------|---------|
|
||||
| Feature completeness | 85% | Strong core, thin edges (staffing, reporting) |
|
||||
| Code quality | 90% | Zero TODOs, clean architecture, typed end-to-end |
|
||||
| Test coverage | 55% | Engine excellent, API routers ~5%, no integration tests |
|
||||
| CI/CD & DevOps | 10% | No pipeline, no prod Docker, no monitoring |
|
||||
| UX polish | 75% | Deep timeline/estimates, but gaps in staffing workflow |
|
||||
| Growth readiness | 40% | No scenario planning, no integrations, no mobile |
|
||||
|
||||
---
|
||||
|
||||
## Wave 1 — Centralize `formatMoney()` / `formatCents()` (Agent: format-consolidator)
|
||||
## Part 1: Bottlenecks
|
||||
|
||||
**Problem:** Zentralisierte Funktionen existieren in `apps/web/src/lib/format.ts`, aber 10+
|
||||
Stellen nutzen Inline-`(x / 100).toLocaleString("de-DE", ...)` statt der zentralen Imports.
|
||||
Zusaetzlich gibt es 2 lokale `fmtEur()` Helfer in API-Routern.
|
||||
### 1.1 Production Readiness Blockers (Critical)
|
||||
|
||||
**Dateien die keine Aenderung brauchen:**
|
||||
- `ShiftPreviewTooltip.tsx` — spezialisierter Delta-Formatter mit +/- Prefix (bleibt)
|
||||
| # | Bottleneck | Impact | Severity |
|
||||
|---|-----------|--------|----------|
|
||||
| B1 | **No CI/CD pipeline** — tests, lint, tsc not automated on PR | Regressions ship undetected | CRITICAL |
|
||||
| B2 | **No production Docker image** — only dev Dockerfile exists | Cannot deploy containerized | CRITICAL |
|
||||
| B3 | **No monitoring/logging** — no Sentry, no Pino, no APM | Blind in production, cannot debug | CRITICAL |
|
||||
| B4 | **No health check endpoints** — `/health`, `/ready` missing | Cannot detect/recover from failures | HIGH |
|
||||
| B5 | **API router test coverage ~5%** — 28 routers, almost no unit tests | Mutations untested at API boundary | HIGH |
|
||||
|
||||
### Tasks
|
||||
### 1.2 UX Bottlenecks
|
||||
|
||||
- [ ] **1.1** Inline-Formatierung in `FillOpenDemandModal.tsx` (5 Stellen, Lines ~214/417/434/444/452) durch `formatCents()` Import ersetzen → `apps/web/src/components/allocations/FillOpenDemandModal.tsx`
|
||||
- [ ] **1.2** Inline-Formatierung in `DemandPopover.tsx` (Lines ~146/152) durch `formatCents()` Import ersetzen → `apps/web/src/components/timeline/DemandPopover.tsx`
|
||||
- [ ] **1.3** Inline-Formatierung in `ResourceHoverCard.tsx` (Lines ~123/130) durch `formatCents()` Import ersetzen → `apps/web/src/components/timeline/ResourceHoverCard.tsx`
|
||||
- [ ] **1.4** Inline-Formatierung in `ProjectWizard.tsx` (Lines ~509-511) ersetzen → `apps/web/src/components/projects/ProjectWizard.tsx`
|
||||
- [ ] **1.5** Inline-Formatierung in `ProjectAssignmentsTable.tsx` (Line ~130) ersetzen → `apps/web/src/components/projects/ProjectAssignmentsTable.tsx`
|
||||
- [ ] **1.6** Inline-Formatierung in `ProjectDemandsTable.tsx` (Lines ~135/138-139) ersetzen → `apps/web/src/components/projects/ProjectDemandsTable.tsx`
|
||||
- [ ] **1.7** Inline-Formatierung in `ProjectsClient.tsx` (Line ~403) ersetzen → `apps/web/src/app/(app)/projects/ProjectsClient.tsx`
|
||||
- [ ] **1.8** Inline-Formatierung in `ProjectTableWidget.tsx` (Lines ~274/283) ersetzen → `apps/web/src/components/dashboard/widgets/ProjectTableWidget.tsx`
|
||||
- [ ] **1.9** Inline-`.toFixed(0)` in `ResourcesClient.tsx` (Line ~1178) und `ResourceDetail.tsx` (Lines ~279/286) durch `formatMoney()` ersetzen → 2 Dateien
|
||||
- [ ] **1.10** Duplizierte `fmtEur()` in `assistant-tools.ts` (Line ~44-46) und `computation-graph.ts` (Line ~53-55) entfernen — gemeinsamen Helfer in `packages/api/src/lib/format-utils.ts` erstellen (da API-Router keinen Zugriff auf `apps/web/src/lib/format.ts` haben) → `packages/api/src/lib/format-utils.ts` (create), 2 Router editieren
|
||||
| # | Bottleneck | Impact | Severity |
|
||||
|---|-----------|--------|----------|
|
||||
| B6 | **Staffing -> Allocation gap** — match results don't link to allocation creation | Users must manually recreate allocations after finding matches | HIGH |
|
||||
| B7 | **Reporting is thin** — only 2 report types (chargeability, PDF allocations) | Finance/PMs can't self-serve custom reports | MEDIUM |
|
||||
| B8 | **No bulk operations in list views** — no multi-select outside timeline | Slow to manage 10+ resources/projects at once | MEDIUM |
|
||||
| B9 | **Dashboard metrics computed live** — no caching/pre-computation | Slow dashboard load with growing data | MEDIUM |
|
||||
| B10 | **Timeline 3.3K LOC ecosystem** — ResourcePanel 1035, ProjectPanel 1315 LOC | Hard to maintain, risky to modify | LOW |
|
||||
|
||||
### Akzeptanzkriterien
|
||||
- Kein `/ 100).toLocaleString("de-DE"` mehr in Component-Dateien (ausser ShiftPreviewTooltip)
|
||||
- Kein lokales `fmtEur()` mehr in API-Routern
|
||||
### 1.3 Architecture Bottlenecks
|
||||
|
||||
| # | Bottleneck | Impact | Severity |
|
||||
|---|-----------|--------|----------|
|
||||
| B11 | **Prisma client cache invalidation** — dev server restart required after schema changes | Developer friction, CI complexity | MEDIUM |
|
||||
| B12 | **No webhook/event outbound** — SSE event bus exists but no external subscriptions | Cannot notify external systems (Slack, Jira) | MEDIUM |
|
||||
| B13 | **No soft-delete strategy** — mixed approach (isActive, status, hard delete) | Data loss risk, no audit trail on deletions | LOW |
|
||||
| B14 | **Rate card lookup manual in estimates** — no auto-lookup by resource chapter/level | Estimate creation slower than needed | LOW |
|
||||
|
||||
---
|
||||
|
||||
## Wave 2 — Adopt `findUniqueOrThrow()` Helper (Agent: db-helper-consolidator)
|
||||
## Part 2: Growth Potential
|
||||
|
||||
**Problem:** `packages/api/src/db/helpers.ts` existiert mit `findUniqueOrThrow<T>()`, aber 7 Router-Dateien nutzen ihn nicht und haben manuelle `findUnique` + `if (!x) throw` Bloecke.
|
||||
### 2.1 High-Value Feature Opportunities
|
||||
|
||||
### Tasks
|
||||
#### Tier 1 — Quick Wins (1-3 days each)
|
||||
|
||||
- [ ] **2.1** `entitlement.ts` — 7 manuelle Stellen durch `findUniqueOrThrow()` ersetzen → `packages/api/src/router/entitlement.ts`
|
||||
- [ ] **2.2** `calculation-rules.ts` — 3 manuelle if+throw Stellen (Lines ~24-26, 62-64, 89-91) ersetzen → `packages/api/src/router/calculation-rules.ts`
|
||||
- [ ] **2.3** `notification.ts` — 3 Stellen migrieren → `packages/api/src/router/notification.ts`
|
||||
- [ ] **2.4** `settings.ts` — 3 Stellen migrieren → `packages/api/src/router/settings.ts`
|
||||
- [ ] **2.5** `user.ts` — verbleibende manuelle Stellen migrieren → `packages/api/src/router/user.ts`
|
||||
- [ ] **2.6** `resource.ts` — 8 verbleibende manuelle Stellen migrieren → `packages/api/src/router/resource.ts`
|
||||
- [ ] **2.7** `vacation.ts` — 12 verbleibende manuelle Stellen migrieren → `packages/api/src/router/vacation.ts`
|
||||
- [ ] **2.8** `timeline.ts` — 4 verbleibende Stellen migrieren → `packages/api/src/router/timeline.ts`
|
||||
- [ ] **2.9** `assistant.ts` — 1 Stelle migrieren → `packages/api/src/router/assistant.ts`
|
||||
| # | Feature | Value | Effort |
|
||||
|---|---------|-------|--------|
|
||||
| G1 | **Staffing "Assign" button** — pre-populate allocation modal from match result | Closes biggest UX gap, saves 5+ clicks per staffing decision | 1-2 days |
|
||||
| G2 | **Dashboard caching** — pre-compute metrics, invalidate on SSE events | 3-5x dashboard load speed improvement | 1-2 days |
|
||||
| G3 | **Bulk list operations** — multi-select + context menu on resources/projects | Enables batch edit, export, status change | 2-3 days |
|
||||
| G4 | **Health check endpoints** — `/api/health` (liveness), `/api/ready` (DB + Redis) | Production deployment prerequisite | 0.5 day |
|
||||
|
||||
**Hinweis:** `assistant-tools.ts` nutzt `{ error: "..." }` Return-Pattern statt throw — NICHT migrieren (anderes Error-Handling).
|
||||
#### Tier 2 — Strategic Features (1-2 weeks each)
|
||||
|
||||
### Akzeptanzkriterien
|
||||
- Alle Router (ausser assistant-tools.ts) nutzen `findUniqueOrThrow()` fuer NOT_FOUND Checks
|
||||
- `pnpm --filter @planarchy/api exec tsc --noEmit` — gruen
|
||||
| # | Feature | Value | Effort |
|
||||
|---|---------|-------|--------|
|
||||
| G5 | **Scenario/What-If Planning** — alternate staffing mixes, cost simulations | Differentiation for PMs and finance; leverages existing engine | 1-2 weeks |
|
||||
| G6 | **Skill Marketplace** — searchable skill inventory, gap heat map, hiring priorities | High leverage from existing skill matrix; enables org-wide planning | 1 week |
|
||||
| G7 | **Custom Report Builder** — drag columns, pivot, grouping, scheduled exports | Unlocks self-service analytics for finance and executives | 1-2 weeks |
|
||||
| G8 | **Collaboration Layer** — inline comments on estimates, @mention, approval feedback | Enables cross-functional workflows (finance, PM, staffing) | 1-2 weeks |
|
||||
|
||||
#### Tier 3 — Market Differentiators (2-4 weeks each)
|
||||
|
||||
| # | Feature | Value | Effort |
|
||||
|---|---------|-------|--------|
|
||||
| G9 | **AI-Powered Insights** — auto-suggest staffing, anomaly detection, narrative reports | Leverages existing Azure OpenAI integration; executive decision support | 2-3 weeks |
|
||||
| G10 | **External Integrations** — Jira/Linear sync, Slack notifications, Google Calendar | Stickiness; connects Planarchy into existing workflows | 2-4 weeks |
|
||||
| G11 | **Mobile Companion** — PWA with quick-view (status, gaps, approvals, push notifications) | Engagement for field PMs and remote staff | 3-4 weeks |
|
||||
| G12 | **Dispo V2 Clean-Slate Import** — design doc + tickets exist, ready for implementation | Unblocks migration from legacy system; critical for customer onboarding | 1-2 weeks |
|
||||
|
||||
### 2.2 Missing Dashboard Widgets
|
||||
|
||||
| Widget | Purpose | Effort |
|
||||
|--------|---------|--------|
|
||||
| Budget spend forecast | Forward-looking actuals vs budget trend line | 2 days |
|
||||
| Team utilization heatmap | Resource x week grid with color intensity | 2 days |
|
||||
| Skill gap analysis | Required vs available skills across open demands | 3 days |
|
||||
| Project health scorecard | On-time, on-budget, quality composite score | 2 days |
|
||||
| Hiring pipeline | Forecast unfilled demand 3-6 months out | 3 days |
|
||||
|
||||
---
|
||||
|
||||
## Wave 3 — Prisma Select Constants adoptieren (Agent: select-consolidator)
|
||||
## Part 3: Automation Potential
|
||||
|
||||
**Problem:** `packages/api/src/db/selects.ts` definiert `ROLE_BRIEF_SELECT`, `PROJECT_BRIEF_SELECT`, `RESOURCE_BRIEF_SELECT`, aber Adoption ist gering (nur `allocation.ts` nutzt alle drei).
|
||||
### 3.1 Development Workflow Automation
|
||||
|
||||
### Tasks
|
||||
| # | Automation | Current State | Target | Effort |
|
||||
|---|-----------|--------------|--------|--------|
|
||||
| A1 | **CI/CD Pipeline** | None | GitHub Actions: test + lint + tsc on PR, build + deploy on merge | 1-2 days |
|
||||
| A2 | **Dependency scanning** | None | Dependabot + npm audit in CI | 0.5 day |
|
||||
| A3 | **E2E test suite expansion** | 4 specs (auth, timeline, projects, resources) | 20+ specs covering key user flows | 1 week |
|
||||
| A4 | **API integration tests** | ~5% router coverage | 80% coverage with mock DB layer | 1-2 weeks |
|
||||
| A5 | **Coverage gates** | Engine 95%, staffing 90%, others none | All packages minimum 80% | 2 days config |
|
||||
|
||||
- [ ] **3.1** `vacation.ts` — 5 Inline-Resource-Selects (Lines ~102/123/213/542/579) durch `RESOURCE_BRIEF_SELECT` ersetzen → `packages/api/src/router/vacation.ts`
|
||||
- [ ] **3.2** `role.ts` — 1 Inline-Resource-Select (Line ~92) ersetzen → `packages/api/src/router/role.ts`
|
||||
- [ ] **3.3** `project-planning-read-model.ts` — 1 Inline-Role-Select (Line ~34) durch `ROLE_BRIEF_SELECT` ersetzen → `packages/api/src/router/project-planning-read-model.ts`
|
||||
- [ ] **3.4** `resource.ts` — 1 Inline-Role-Select (Line ~313) durch `ROLE_BRIEF_SELECT` ersetzen → `packages/api/src/router/resource.ts`
|
||||
- [ ] **3.5** `calculation-rules.ts` — 2 Inline-Project-Selects (Lines ~13/22) durch `PROJECT_BRIEF_SELECT` ersetzen → `packages/api/src/router/calculation-rules.ts`
|
||||
- [ ] **3.6** `entitlement.ts` — 1 Inline-Resource-Select (Line ~269) durch `RESOURCE_BRIEF_SELECT` (+ spread `chapter: true`) ersetzen → `packages/api/src/router/entitlement.ts`
|
||||
### 3.2 Business Process Automation
|
||||
|
||||
### Akzeptanzkriterien
|
||||
- Keine `{ id: true, name: true, color: true }` Inline-Selects mehr in Routern (ausser dort wo erweitert)
|
||||
- `pnpm --filter @planarchy/api exec tsc --noEmit` — gruen
|
||||
| # | Automation | Current Manual Process | Automated Process | Effort |
|
||||
|---|-----------|----------------------|-------------------|--------|
|
||||
| A6 | **Auto-staffing suggestions** | PM manually searches for resources per demand | System proposes top-3 matches when demand is created | 3 days |
|
||||
| A7 | **Vacation conflict alerts** | Manager manually checks team calendar before approving | Auto-detect overlap > threshold, flag in approval flow | 2 days |
|
||||
| A8 | **Budget overrun notifications** | Finance checks dashboards manually | SSE-triggered notification when project hits 80%/100% budget | 1 day |
|
||||
| A9 | **Estimate approval reminders** | Verbal follow-up | Scheduled notification after N days in SUBMITTED status | 1 day |
|
||||
| A10 | **Chargeability alerts** | Monthly manual review | Weekly auto-email when resource chargeability drops below target | 2 days |
|
||||
| A11 | **Rate card auto-apply** | Manual rate lookup when creating estimate demand lines | Auto-fill LCR/UCR from rate card by resource chapter + level + client | 2 days |
|
||||
| A12 | **Public holiday auto-import** | Admin manually batch-creates per year | Auto-generate on year rollover based on country/state config | 1 day |
|
||||
|
||||
### 3.3 Monitoring & Observability Automation
|
||||
|
||||
| # | Automation | Target | Effort |
|
||||
|---|-----------|--------|--------|
|
||||
| A13 | **Structured logging** (Pino) | All API requests logged with correlation ID | 2 days |
|
||||
| A14 | **Error tracking** (Sentry) | Unhandled exceptions captured with context | 1 day |
|
||||
| A15 | **Performance monitoring** | Slow query detection, API response time tracking | 2 days |
|
||||
| A16 | **Uptime monitoring** | External health check probe, alerting | 0.5 day |
|
||||
|
||||
---
|
||||
|
||||
## Wave 4 — Status Badge & Vacation Constant Consolidation (Agent: style-consolidator)
|
||||
## Part 4: Prioritized Roadmap
|
||||
|
||||
**Problem:** Mehrere duplizierte Konstanten-Maps fuer Status-Badges und Vacation-Type-Farben:
|
||||
- `VACATION_TYPE_LABELS` dupliziert in `VacationModal.tsx` (Line ~13-18)
|
||||
- `TYPE_COLORS` + `TYPE_BORDER` + `TYPE_LABELS_SHORT` identisch in `TimelineResourcePanel.tsx` (Lines ~563-580) und `TimelineProjectPanel.tsx` (Lines ~1299-1316)
|
||||
- `TYPE_COLOR` fuer Kalender dupliziert in `VacationCalendar.tsx` (Line ~21) und `TeamCalendar.tsx` (Line ~8) — mit Inkonsistenz bei PUBLIC_HOLIDAY
|
||||
### Sprint 0: Production Foundation (Week 1)
|
||||
|
||||
### Tasks
|
||||
**Goal:** Unblock production deployment.
|
||||
|
||||
- [ ] **4.1** Vacation-Timeline-Konstanten (`VACATION_TIMELINE_COLORS`, `VACATION_TIMELINE_BORDER`, `VACATION_TYPE_LABELS_SHORT`) in `status-styles.ts` als Exports hinzufuegen → `apps/web/src/lib/status-styles.ts`
|
||||
- [ ] **4.2** Vacation-Kalender-Konstanten (`VACATION_CALENDAR_COLORS`) in `status-styles.ts` hinzufuegen, PUBLIC_HOLIDAY-Inkonsistenz auf `emerald-500` vereinheitlichen → `apps/web/src/lib/status-styles.ts`
|
||||
- [ ] **4.3** `TimelineResourcePanel.tsx` — lokale `TYPE_COLORS/TYPE_BORDER/TYPE_LABELS_SHORT` (Lines ~563-580) entfernen, Import aus `status-styles.ts` → `apps/web/src/components/timeline/TimelineResourcePanel.tsx`
|
||||
- [ ] **4.4** `TimelineProjectPanel.tsx` — lokale `TYPE_COLORS/TYPE_BORDER/TYPE_LABELS_SHORT` (Lines ~1299-1316) entfernen, Import aus `status-styles.ts` → `apps/web/src/components/timeline/TimelineProjectPanel.tsx`
|
||||
- [ ] **4.5** `VacationModal.tsx` — lokales `VACATION_TYPE_LABELS` (Lines ~13-18) entfernen, Import aus `status-styles.ts` → `apps/web/src/components/vacations/VacationModal.tsx`
|
||||
- [ ] **4.6** `VacationCalendar.tsx` — lokales `TYPE_COLOR` (Line ~21) ersetzen durch Import → `apps/web/src/components/vacations/VacationCalendar.tsx`
|
||||
- [ ] **4.7** `TeamCalendar.tsx` — lokales `TYPE_COLOR` (Line ~8) ersetzen durch Import → `apps/web/src/components/vacations/TeamCalendar.tsx`
|
||||
- [ ] **A1** — GitHub Actions CI pipeline (test + lint + tsc + build)
|
||||
- [ ] **G4** — Health check endpoints (`/api/health`, `/api/ready`)
|
||||
- [ ] **A14** — Sentry error tracking integration
|
||||
- [ ] **A13** — Pino structured logging in API layer
|
||||
- [ ] Production Dockerfile (multi-stage, distroless base)
|
||||
- [ ] docker-compose.prod.yml with env-based config
|
||||
- [ ] Database backup strategy (pg_dump cron + S3)
|
||||
|
||||
### Akzeptanzkriterien
|
||||
- Keine duplizierten Vacation/Status-Konstanten mehr in Komponenten
|
||||
- `VacationCalendar` und `TeamCalendar` nutzen identische Farben
|
||||
**Acceptance:** `main` branch has green CI, production image builds, errors are captured.
|
||||
|
||||
### Sprint 1: Quick Wins (Week 2)
|
||||
|
||||
**Goal:** Close the biggest UX gaps and improve daily workflows.
|
||||
|
||||
- [ ] **G1** — Staffing "Assign" button (match -> allocation in 1 click)
|
||||
- [ ] **G2** — Dashboard metric caching (Redis-backed, SSE-invalidated)
|
||||
- [ ] **G3** — Bulk operations on resource/project lists
|
||||
- [ ] **A8** — Budget overrun notifications (80% + 100% thresholds)
|
||||
- [ ] **A9** — Estimate approval reminders (auto-notify after 3 days)
|
||||
|
||||
**Acceptance:** Staffing-to-allocation is 1 click, dashboard loads <500ms, bulk select works.
|
||||
|
||||
### Sprint 2: Test Coverage & Stability (Week 3)
|
||||
|
||||
**Goal:** Harden the codebase for confident iteration.
|
||||
|
||||
- [ ] **A4** — API router integration tests (target 15 most-used routers)
|
||||
- [ ] **A5** — Coverage gates: api + application packages at 80%
|
||||
- [ ] **A3** — E2E expansion: 10 new specs (estimate lifecycle, vacation flow, bulk ops, filters)
|
||||
- [ ] **A2** — Dependabot + npm audit in CI
|
||||
|
||||
**Acceptance:** `pnpm test:unit` covers all routers, E2E suite runs in CI, zero high-severity vulnerabilities.
|
||||
|
||||
### Sprint 3: Automation & Intelligence (Week 4-5)
|
||||
|
||||
**Goal:** Automate repetitive decisions, surface insights proactively.
|
||||
|
||||
- [ ] **A6** — Auto-staffing suggestions on demand creation
|
||||
- [ ] **A7** — Vacation conflict detection in approval flow
|
||||
- [ ] **A10** — Weekly chargeability alerts
|
||||
- [ ] **A11** — Rate card auto-apply in estimate demand lines
|
||||
- [ ] **A12** — Public holiday auto-import on year rollover
|
||||
- [ ] **G6** — Skill marketplace MVP (searchable inventory + gap heat map)
|
||||
|
||||
**Acceptance:** Demands auto-suggest resources, vacation conflicts auto-flagged, rate cards auto-filled.
|
||||
|
||||
### Sprint 4: Strategic Features (Week 6-8)
|
||||
|
||||
**Goal:** Build differentiation features that create competitive moat.
|
||||
|
||||
- [ ] **G5** — Scenario/what-if planning (staffing mix simulator)
|
||||
- [ ] **G7** — Custom report builder MVP (column picker, filters, export)
|
||||
- [ ] **G8** — Collaboration layer (comments on estimates, @mention)
|
||||
- [ ] **G12** — Dispo V2 clean-slate import (leverage existing design docs + tickets)
|
||||
- [ ] Dashboard new widgets: budget forecast, skill gap, project health scorecard
|
||||
|
||||
**Acceptance:** PMs can simulate staffing scenarios, finance can build custom reports, Dispo import onboards first customer.
|
||||
|
||||
### Sprint 5: Market Expansion (Week 9-12)
|
||||
|
||||
**Goal:** Expand the platform beyond core planning.
|
||||
|
||||
- [ ] **G9** — AI insights: auto-staffing, anomaly detection, narrative summaries
|
||||
- [ ] **G10** — Jira/Linear integration + Slack notifications
|
||||
- [ ] **G11** — Mobile PWA companion
|
||||
- [ ] **A15** — Performance monitoring + load testing baseline
|
||||
- [ ] Advanced: multi-tenant architecture planning
|
||||
|
||||
**Acceptance:** AI suggestions active, Jira sync live, mobile app installable.
|
||||
|
||||
---
|
||||
|
||||
## Wave 5 — Adopt `useInvalidatePlanningViews()` Hook (Agent: invalidation-consolidator)
|
||||
## Part 5: Risk Register
|
||||
|
||||
**Problem:** Hook existiert in `apps/web/src/hooks/useInvalidatePlanningViews.ts` mit 8 Queries,
|
||||
wird aber von KEINER Mutation genutzt. 14+ Stellen kopieren die 4-Query-Timeline-Invalidierung manuell.
|
||||
Ausserdem fehlt `getProjectContext` in `useTimelineDrag.ts` (Line ~238).
|
||||
|
||||
### Tasks
|
||||
|
||||
- [ ] **5.1** `useInvalidatePlanningViews` in eine `useInvalidateTimeline()` (4 Timeline-Queries) und `useInvalidateAllAllocViews()` (alle 8) aufspalten, da manche Stellen nur Timeline invalidieren → `apps/web/src/hooks/useInvalidatePlanningViews.ts` (edit)
|
||||
- [ ] **5.2** `TimelineView.tsx` — 2 manuelle Invalidierungsbloecke (Lines ~73-76, ~333-336) durch Hook ersetzen → `apps/web/src/components/timeline/TimelineView.tsx`
|
||||
- [ ] **5.3** `AllocationPopover.tsx` — Invalidierungsblock (Lines ~58-62) durch Hook ersetzen → `apps/web/src/components/timeline/AllocationPopover.tsx`
|
||||
- [ ] **5.4** `NewAllocationPopover.tsx` — Invalidierungsblock (Lines ~63-66) ersetzen → `apps/web/src/components/timeline/NewAllocationPopover.tsx`
|
||||
- [ ] **5.5** `BatchAssignPopover.tsx` — Invalidierungsblock (Lines ~54-57) ersetzen → `apps/web/src/components/timeline/BatchAssignPopover.tsx`
|
||||
- [ ] **5.6** `ProjectPanel.tsx` — 3 Invalidierungsbloecke (Lines ~106-109, 115-118, 124-127) ersetzen → `apps/web/src/components/timeline/ProjectPanel.tsx`
|
||||
- [ ] **5.7** `useAllocationHistory.ts` — 3 Invalidierungsbloecke (Lines ~31-34, 40-43, 62-65) ersetzen → `apps/web/src/hooks/useAllocationHistory.ts`
|
||||
- [ ] **5.8** `useTimelineDrag.ts` — 2 Bloecke (Lines ~238-241, 254-257) ersetzen + fehlende `getProjectContext` Invalidierung fixen → `apps/web/src/hooks/useTimelineDrag.ts`
|
||||
- [ ] **5.9** `FillOpenDemandModal.tsx` — Invalidierungsblock (Lines ~76-79) ersetzen → `apps/web/src/components/allocations/FillOpenDemandModal.tsx`
|
||||
|
||||
### Akzeptanzkriterien
|
||||
- Keine manuellen 4-Query-Timeline-Invalidierungsbloecke mehr
|
||||
- `useTimelineDrag.ts` invalidiert alle 4 Timeline-Queries (inkl. `getProjectContext`)
|
||||
| # | Risk | Probability | Impact | Mitigation |
|
||||
|---|------|-------------|--------|------------|
|
||||
| R1 | Production deployment without CI catches regressions | HIGH | CRITICAL | Sprint 0 is mandatory before any feature work |
|
||||
| R2 | Timeline 3.3K LOC becomes unmaintainable | MEDIUM | HIGH | Decompose into sub-hook modules when next touching timeline |
|
||||
| R3 | Dashboard performance degrades with data growth | MEDIUM | MEDIUM | G2 (caching) in Sprint 1; monitor query times |
|
||||
| R4 | Prisma schema changes break dev workflow | HIGH | LOW | Automate restart in dev scripts (already documented) |
|
||||
| R5 | Skill matrix AI costs grow with usage | LOW | MEDIUM | Add token budget tracking in SystemSettings |
|
||||
| R6 | No data backup strategy | MEDIUM | CRITICAL | Add pg_dump cron + S3 upload in Sprint 0 |
|
||||
| R7 | Single-point-of-failure (1 dev, 1 server) | HIGH | CRITICAL | Document architecture, automate deployment, enable team onboarding |
|
||||
|
||||
---
|
||||
|
||||
## Wave 6 — Timeline Render-Helpers & React.memo (Agent: timeline-optimizer)
|
||||
## Part 6: Key Metrics to Track
|
||||
|
||||
**Problem:** 3 identische Render-Funktionen in beiden Panel-Komponenten (Vacation-Blocks, Range-Overlay, Overbooking-Blink). Keine `React.memo` auf den grossen Panel-Komponenten. `useTimelineDrag.ts` ist 883 Zeilen Monolith.
|
||||
### Product Metrics
|
||||
- **Time-to-staff**: Minutes from demand creation to resource assignment
|
||||
- **Estimate turnaround**: Days from estimate creation to approval
|
||||
- **Vacation approval latency**: Hours from request to decision
|
||||
- **Dashboard load time**: P95 response time for dashboard page
|
||||
- **Chargeability accuracy**: Forecast vs actual deviation %
|
||||
|
||||
### Tasks
|
||||
|
||||
- [ ] **6.1** `renderHelpers.ts` erstellen mit extrahierten Shared-Funktionen: `renderVacationBlocks()`, `renderRangeOverlay()`, `renderOverbookingBlink()` → `apps/web/src/components/timeline/renderHelpers.ts` (create)
|
||||
- [ ] **6.2** `TimelineResourcePanel.tsx` — lokale `renderVacationBlocksForRow`, `renderRangeOverlay`, `renderOverbookingBlink` (Lines ~582-636, 895-930) durch Imports aus `renderHelpers.ts` ersetzen → edit
|
||||
- [ ] **6.3** `TimelineProjectPanel.tsx` — lokale `renderVacationBlocksForProjectRow`, `renderRangeOverlayProject`, `renderOverbookingBlinkProject` durch Imports ersetzen → edit
|
||||
- [ ] **6.4** `React.memo()` auf `TimelineResourcePanel` wrappen → `TimelineResourcePanel.tsx`
|
||||
- [ ] **6.5** `React.memo()` auf `TimelineProjectPanel` wrappen → `TimelineProjectPanel.tsx`
|
||||
- [ ] **6.6** Multi-Select-Intersection-Logic (Lines ~573-634 in `TimelineView.tsx`) in eigenen Hook `useMultiSelectIntersection.ts` extrahieren → `apps/web/src/hooks/useMultiSelectIntersection.ts` (create), `TimelineView.tsx` (edit)
|
||||
- [ ] **6.7** `useTimelineDrag.ts` — Drag-Math-Utilities (`pixelsToDays`, `constrainToGrid`, `clampDate`) in `dragMath.ts` extrahieren → `apps/web/src/components/timeline/dragMath.ts` (create), `useTimelineDrag.ts` (edit)
|
||||
|
||||
### Akzeptanzkriterien
|
||||
- Keine duplizierten Render-Funktionen zwischen den Panel-Komponenten
|
||||
- Beide Panels als `React.memo()` exportiert
|
||||
- `useTimelineDrag.ts` unter 800 Zeilen
|
||||
- Timeline rendert korrekt in beiden Views + Overbooking-Blink funktioniert
|
||||
### Engineering Metrics
|
||||
- **Test coverage**: % by package (target: all >=80%)
|
||||
- **CI green rate**: % of PRs passing all gates
|
||||
- **Build time**: Minutes for full `next build`
|
||||
- **Error rate**: Sentry exceptions per hour
|
||||
- **API latency**: P95 tRPC procedure response time
|
||||
|
||||
---
|
||||
|
||||
## Wave 7 — Composite Database Indexes (Agent: db-index-optimizer)
|
||||
## Appendix: Current State Snapshot
|
||||
|
||||
**Problem:** Mehrere haeufig abgefragte Modelle haben keine optimalen Composite-Indexes fuer
|
||||
die kombinierten WHERE-Bedingungen der tRPC-Router.
|
||||
|
||||
### Tasks
|
||||
|
||||
- [ ] **7.1** `DemandRequirement` — `@@index([projectId, status, startDate, endDate])` hinzufuegen (ersetzt/ergaenzt bestehenden 3-Feld-Index) → `packages/db/prisma/schema.prisma`
|
||||
- [ ] **7.2** `Resource` — `@@index([isActive, orgUnitId])` hinzufuegen fuer "aktive Ressourcen pro OrgUnit" Queries → `packages/db/prisma/schema.prisma`
|
||||
- [ ] **7.3** `Project` — `@@index([status, startDate, endDate])` hinzufuegen fuer Timeline-Filterung → `packages/db/prisma/schema.prisma`
|
||||
- [ ] **7.4** `Estimate` — `@@index([projectId, status])` hinzufuegen → `packages/db/prisma/schema.prisma`
|
||||
- [ ] **7.5** `pnpm db:push` ausfuehren, `.next/` Cache loeschen, Dev-Server neu starten
|
||||
|
||||
**Bereits optimal:**
|
||||
- `Vacation` — hat `@@index([resourceId, status, startDate, endDate])` ✅
|
||||
- `Assignment` — hat `@@index([resourceId, status, startDate])` + `@@index([projectId, startDate, endDate])` ✅
|
||||
|
||||
### Akzeptanzkriterien
|
||||
- Neue Indexes in Schema sichtbar
|
||||
- `pnpm db:push` erfolgreich
|
||||
- Dev-Server startet sauber
|
||||
|
||||
---
|
||||
|
||||
## Abhaengigkeiten & Parallelisierung
|
||||
|
||||
```
|
||||
Wave 1 (format) ─┐
|
||||
Wave 2 (findUniqueOrThrow) ─┤
|
||||
Wave 3 (selects) ─┤── Alle parallel ausfuehrbar (verschiedene Dateien/Domains)
|
||||
Wave 4 (status-styles) ─┤
|
||||
Wave 5 (invalidation) ─┤
|
||||
Wave 6 (timeline render) ─┘
|
||||
|
||||
Wave 7 (DB indexes) ── Sequentiell NACH Wave 1-6 (erfordert db:push + Restart)
|
||||
```
|
||||
|
||||
**Innerhalb der Waves:**
|
||||
- Wave 1: Task 1.10 (API format-utils) kann parallel zu Tasks 1.1-1.9 (web components)
|
||||
- Wave 2: Alle Tasks unabhaengig (verschiedene Router-Dateien)
|
||||
- Wave 4: Task 4.1-4.2 ZUERST (erstellt Exports), dann Tasks 4.3-4.7 parallel
|
||||
- Wave 5: Task 5.1 ZUERST (Hook-Refactoring), dann Tasks 5.2-5.9 parallel
|
||||
- Wave 6: Task 6.1 ZUERST (erstellt renderHelpers.ts), dann Tasks 6.2-6.7 parallel
|
||||
|
||||
**Dateikonflikte vermeiden:**
|
||||
- Wave 2 und Wave 3 editieren teilweise gleiche Router-Dateien (`vacation.ts`, `entitlement.ts`, `calculation-rules.ts`) — diese Tasks SEQUENTIELL innerhalb eines Agents ausfuehren
|
||||
- Wave 4 und Wave 6 editieren beide Panel-Dateien — unterschiedliche Abschnitte, koennen aber sicherer sequentiell sein
|
||||
|
||||
---
|
||||
|
||||
## Akzeptanzkriterien (Gesamt)
|
||||
|
||||
- [ ] `pnpm --filter @planarchy/web exec tsc --noEmit` — null Errors
|
||||
- [ ] `pnpm --filter @planarchy/api exec tsc --noEmit` — null Errors
|
||||
- [ ] `pnpm --filter @planarchy/engine exec vitest run` — alle Tests gruen
|
||||
- [ ] `pnpm --filter @planarchy/staffing exec vitest run` — alle Tests gruen
|
||||
- [ ] Dev-Server startet, Timeline rendert in beiden Views
|
||||
- [ ] Overbooking-Blink funktioniert
|
||||
- [ ] Demand-Popover und Resource-Hover-Card funktionieren
|
||||
- [ ] Keine duplizierten `formatMoney/formatCents/fmtEur` Funktionsdefinitionen
|
||||
- [ ] Keine manuellen `findUnique` + throw Bloecke in Routern (ausser assistant-tools.ts)
|
||||
- [ ] Keine duplizierten Vacation-Type/Status-Konstanten
|
||||
- [ ] Keine manuellen 4-Query Timeline-Invalidierungsbloecke
|
||||
|
||||
---
|
||||
|
||||
## Risiken & Offene Fragen
|
||||
|
||||
1. **Wave 2+3 Dateikonflikt:** `vacation.ts`, `entitlement.ts`, `calculation-rules.ts` werden in Wave 2 UND Wave 3 editiert. Loesung: Ein Agent bearbeitet beide Waves sequentiell fuer diese Dateien.
|
||||
2. **Wave 5 Type-Cast:** `useInvalidatePlanningViews` hat einen TypeScript-Cast fuer `allocation.listView`. Wenn der Cast nach Refactoring bricht, muss der tRPC-Output-Type geprueft werden.
|
||||
3. **Wave 6 memo-Props:** `React.memo()` auf Panels erfordert stabile Prop-Referenzen. Wenn Inline-Callbacks als Props uebergeben werden, muss der Parent `useCallback` nutzen — pruefe `TimelineViewContent`.
|
||||
4. **Wave 7 DB-Migration:** `db:push` auf Produktions-DB erfordert Maintenance-Window fuer Index-Erstellung. Auf Dev-DB unproblematisch.
|
||||
5. **assistant-tools.ts:** 45 `findUnique` Stellen mit `{ error: "..." }` Return-Pattern — NICHT auf `findUniqueOrThrow` migrieren, da das Error-Handling grundlegend anders ist (Return vs Throw).
|
||||
|
||||
---
|
||||
|
||||
## Metriken (erwartet)
|
||||
|
||||
| Metrik | Vorher | Nachher |
|
||||
|--------|--------|---------|
|
||||
| Duplizierte Format-Funktionen | 12+ Inline | 0 (1 zentrale Lib + 1 API-Helfer) |
|
||||
| Manuelle findUnique+throw | ~35 Stellen | 0 (alle via Helper) |
|
||||
| Inline Prisma-Selects | ~20 Duplikate | 0 (via Shared Constants) |
|
||||
| Duplizierte Status-Konstanten | 7 Stellen | 0 (1 zentrale Datei) |
|
||||
| Manuelle Invalidierungsbloecke | 14+ Stellen | 0 (via Hooks) |
|
||||
| Duplizierte Render-Funktionen | 3 Paare (6 total) | 3 Shared (renderHelpers.ts) |
|
||||
| useTimelineDrag.ts Zeilen | 883 | ~800 |
|
||||
| Fehlende DB-Composite-Indexes | 4 | 0 |
|
||||
| Dimension | Count |
|
||||
|-----------|-------|
|
||||
| Database models | 47 |
|
||||
| tRPC routers | 28 |
|
||||
| tRPC procedures | ~200 (120Q + 80M) |
|
||||
| Frontend routes | 34 |
|
||||
| Domain components | 109+ |
|
||||
| Shared UI components | 20+ |
|
||||
| Unit test files | 62 |
|
||||
| E2E test specs | 4 |
|
||||
| Engine test coverage | 95% (gated) |
|
||||
| Staffing test coverage | 90% (gated) |
|
||||
| API router test coverage | ~5% (not gated) |
|
||||
| CI/CD pipeline | None |
|
||||
| Production Docker | None |
|
||||
| Monitoring/APM | None |
|
||||
| Completed phases | 9 |
|
||||
| Known pain points | 24 (documented in LEARNINGS.md) |
|
||||
|
||||
Reference in New Issue
Block a user