wulf-pulse/CLAUDE.md

500 lines
30 KiB
Markdown
Raw Normal View History

# Pulse — Repo Guide for Claude
Pulse is an internal PSA management dashboard for Wulf Consulting. It syncs Autotask
data into Postgres and adds dashboards, workflows, and analytics around it.
Single Next.js 16 app — not a monorepo.
`README.md` covers the human-facing overview. **Trust this file** for the details
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable Major UI refresh on the nav-design-improvements branch. Drops 2013-era inline styles and consolidates patterns behind shared primitives. Foundation - New Wulf brand layer in app/styles/brand.css repointing --primary to the standards-guide blue (#0075AD) with utility classes for numerics (.num / .num-lg / .num-xl), metric labels, surface tints, and the wolf-mark watermark - Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font; Helvetica/Arial stays in the fallback chain for brand fidelity - Wordmark subtitle changed from "PSA Management System" to "Operations console" everywhere it appeared - Tagline footer ("Don't be afraid to cry") on every non-mobile page Status moved out of /dashboard - New /status route with integration tiles grouped by category, sync health table, worker pulse cards (analyzer / RMM / sync scheduler), token-expiry section, conditional alert banner - Top-bar StatusIndicator polls integration health every 60s and links to /status - INTEGRATIONS_DISABLED env var suppresses operator-disabled integrations (e.g. SentinelOne) — no failure noise from broken-on- purpose entries. Aliases supported (sentinelone → s1, etc.) Dashboard rebuilt around KPIs - /api/dashboard/overview adds today snapshot (opened, resolved, open total, SLA breaches) with delta math - /api/dashboard/trends backs queue × priority heatmap, 30-day volume area chart, 30-day mean resolution time line chart, today's active engineers leaderboard Components - StatusBadge driven by lib/status-registry.ts (priority, ticket status, classification, source, company type, publish, active / yes-no / billable / approved registries) - StatusLight (8px geometric square, five states, three sizes) - EmptyState (shared dashed panel with icon + headline + optional CTA) - KpiCard with delta indicator and tonal left border - WulfMark (mark / wordmark variants from /public/branding) - Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table) Navigation - Admin flat link → dropdown with seven shortcuts - New UserMenu (initials avatar, role badge, settings + sign-out) - Active-route highlight is now a 2px Wulf-blue underline echoing the PageHeader rule (consistent across flat links and submenu triggers); active children inside dropdowns use bg-primary/10 - Submenu width is content-driven (min-w 320 / max-w 440, single col) - Mobile hamburger via Sheet, reuses the same nav config Pages migrated - 16 admin sub-pages adopt PageHeader (with accent prop) - /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges - 10 raw <table> blocks across admin/sync/* migrated to shadcn Table - /veeam-analysis migrated to shadcn Table (kept its expansion logic) - Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs DataTable - Rewritten on @tanstack/react-table v8 in manual mode; external API unchanged so all 10+ data-browser pages keep working - New optional props for drill-down rows: getRowCanExpand + renderSubRow Mobile - Multi-select Popover gets max-w-[calc(100vw-1rem)] and collisionPadding so dropdowns can't overflow narrow viewports - CI filter bar wraps and shrinks; stat pill flows below Docs - New ARCHITECTURE.md (load-bearing reference for runtime, data flow, workers, analyzer pipeline, auth, deployment, gotchas) - New DESIGN.md (tokens, layout, navigation IA, component vocabulary, rolling backlog of remaining cleanup) - CLAUDE.md refreshed with pointers to the two new docs and the INTEGRATIONS_DISABLED operator config note - shadcn registry registered as project-level MCP server (.mcp.json) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
that matter to coding decisions. For deeper context:
- **`ARCHITECTURE.md`** — runtime topology, data flow, workers, analyzer pipeline,
invariants. Read before touching workers, sync, or the analyzer.
- **`DESIGN.md`** — design tokens, navigation IA, component vocabulary, layout
rules, and the working backlog for nav/visual cleanup. Read before touching
pages or shared UI components.
## Stack
- Next.js 16 + React 19 (App Router, `reactCompiler: true`, `output: 'standalone'`)
- TypeScript strict, path alias `@/*`
- Postgres 16 via `pg` (no ORM), Redis (caching), Better Auth 1.4
- Tailwind 4, shadcn/ui (`components/ui/`), recharts, sonner, lucide
- Forms: react-hook-form + Zod resolver — admin/auth forms only
- Runs on **port 3100** (Docker exposes 3100; `BETTER_AUTH_URL=http://localhost:3100`)
## Layout
- `app/` — App Router pages + `app/api/**/route.ts` handlers
- `lib/services/` — integration clients, sync services, scheduler (~50 files)
- `lib/types/<domain>.ts` — shared types (autotask, sync, veeam, workflow, …)
- `lib/auth.ts`, `lib/auth-utils.ts`, `lib/permissions.ts` — auth wiring
- `components/ui/` — shadcn primitives; sibling dirs are feature components
- `migrations/NNN_*.sql` — numbered SQL, applied in alphabetical order on Postgres
init. Use `IF NOT EXISTS` + `ON CONFLICT DO NOTHING` for seed data.
- `docs/` — long-form integration/sync guides; reference these, don't duplicate.
- `scripts/` — one-off ops scripts, not tests.
## Database
- All columns are **`snake_case`**. API responses are **`camelCase`** — handlers
transform manually (no ORM).
- Use the `postgresClient` singleton from `lib/services/postgres-client.ts`:
`postgresClient.query(sql, params)`, `.transaction()`, `.upsert()`, `.bulkUpsert()`.
- Audit columns convention: `created_at`, `updated_at`, `synced_at`, `is_deleted`,
`deleted_at`.
- Adding a migration: next number, `IF NOT EXISTS`, no destructive ops on existing
data without a guard. Postgres init applies them on first boot only — for an
existing DB, run via `scripts/apply-migrations` (check first; behavior varies).
## API routes
- Pattern: `app/api/<resource>/route.ts` exporting `GET`/`POST`/etc.
- No Zod validation in route handlers today. Validate inputs explicitly when it
matters; don't add a framework just to validate one field.
- Errors: `try/catch`, return `NextResponse.json({ error, message }, { status })`.
Convention: 503 for missing/bad config, 401/403 from auth helpers, 500 for runtime.
- **Auth in API routes**: import from `lib/auth-utils.ts`:
```ts
const { session, error } = await requireAuth(); // or requireAdmin() / requireSuperAdmin() / requirePermission(resource, action)
if (error) return error;
```
`middleware.ts` only verifies a session cookie exists — role checks happen here.
- No `'use server'` actions in this codebase. Everything is API routes called from
client components via `fetch`.
## Frontend
- Most pages are `'use client'` with `useState`/`useEffect`/`fetch('/api/...')`.
No SWR/react-query — don't introduce one for one-off fetches; match the
surrounding code.
- Server components are fine for static shells; data calls live on the client today.
- Toasts: `sonner`. Tables: `@tanstack/react-table` via `components/admin/DataTable.tsx`.
Modals: `components/admin/DetailModal.tsx`. Navigation: `components/navigation/app-navigation.tsx`.
## External integrations
All clients live in `lib/services/` with a factory + `is<Name>Configured()` helper.
Examples: `getAutotaskClient()`, `getMsgraphClient()`, `getDattoRmmClient()`,
`getVeeamClient()`. Credentials always come from env vars; clients throw if missing.
| Service | Env prefix |
|---|---|
| Autotask | `AUTOTASK_*` (incl. `AUTOTASK_WEBHOOK_SECRET`) |
| MS Graph (app) | `MSGRAPH_*` (specific tenant, not `common`) |
| Microsoft OAuth (login) | `MICROSOFT_*` |
| Datto RMM | `DATTO_RMM_*` |
| Veeam VSPC | `VEEAM_VSPC_*` |
| Auvik / Addigy / IT Glue / Mimecast / S1 / Duo / Zoom / QBO / Zabbix / Salesbldr | `<NAME>_*` |
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable Major UI refresh on the nav-design-improvements branch. Drops 2013-era inline styles and consolidates patterns behind shared primitives. Foundation - New Wulf brand layer in app/styles/brand.css repointing --primary to the standards-guide blue (#0075AD) with utility classes for numerics (.num / .num-lg / .num-xl), metric labels, surface tints, and the wolf-mark watermark - Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font; Helvetica/Arial stays in the fallback chain for brand fidelity - Wordmark subtitle changed from "PSA Management System" to "Operations console" everywhere it appeared - Tagline footer ("Don't be afraid to cry") on every non-mobile page Status moved out of /dashboard - New /status route with integration tiles grouped by category, sync health table, worker pulse cards (analyzer / RMM / sync scheduler), token-expiry section, conditional alert banner - Top-bar StatusIndicator polls integration health every 60s and links to /status - INTEGRATIONS_DISABLED env var suppresses operator-disabled integrations (e.g. SentinelOne) — no failure noise from broken-on- purpose entries. Aliases supported (sentinelone → s1, etc.) Dashboard rebuilt around KPIs - /api/dashboard/overview adds today snapshot (opened, resolved, open total, SLA breaches) with delta math - /api/dashboard/trends backs queue × priority heatmap, 30-day volume area chart, 30-day mean resolution time line chart, today's active engineers leaderboard Components - StatusBadge driven by lib/status-registry.ts (priority, ticket status, classification, source, company type, publish, active / yes-no / billable / approved registries) - StatusLight (8px geometric square, five states, three sizes) - EmptyState (shared dashed panel with icon + headline + optional CTA) - KpiCard with delta indicator and tonal left border - WulfMark (mark / wordmark variants from /public/branding) - Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table) Navigation - Admin flat link → dropdown with seven shortcuts - New UserMenu (initials avatar, role badge, settings + sign-out) - Active-route highlight is now a 2px Wulf-blue underline echoing the PageHeader rule (consistent across flat links and submenu triggers); active children inside dropdowns use bg-primary/10 - Submenu width is content-driven (min-w 320 / max-w 440, single col) - Mobile hamburger via Sheet, reuses the same nav config Pages migrated - 16 admin sub-pages adopt PageHeader (with accent prop) - /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges - 10 raw <table> blocks across admin/sync/* migrated to shadcn Table - /veeam-analysis migrated to shadcn Table (kept its expansion logic) - Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs DataTable - Rewritten on @tanstack/react-table v8 in manual mode; external API unchanged so all 10+ data-browser pages keep working - New optional props for drill-down rows: getRowCanExpand + renderSubRow Mobile - Multi-select Popover gets max-w-[calc(100vw-1rem)] and collisionPadding so dropdowns can't overflow narrow viewports - CI filter bar wraps and shrinks; stat pill flows below Docs - New ARCHITECTURE.md (load-bearing reference for runtime, data flow, workers, analyzer pipeline, auth, deployment, gotchas) - New DESIGN.md (tokens, layout, navigation IA, component vocabulary, rolling backlog of remaining cleanup) - CLAUDE.md refreshed with pointers to the two new docs and the INTEGRATIONS_DISABLED operator config note - shadcn registry registered as project-level MCP server (.mcp.json) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
| Anthropic | `ANTHROPIC_API_KEY` (analyzer pipeline + `ai-triage-service.ts`) |
| OpenRouter | `OPENROUTER_API_KEY` (alternate analyzer provider, opt-in per request) |
| Backblaze B2 | `B2_*` (LogLift evidence storage) |
| Postgres / Redis | `POSTGRES_*` or `DATABASE_URL`, `REDIS_URL` |
## Sync & scheduling
- `lib/services/entity-sync.ts` — per-entity Autotask → Postgres sync (incremental
via `lastTrackedModificationDateTime` when supported, else full upsert).
- `lib/services/sync-scheduler.ts` — node-cron singleton. **Self-initializes on
first server-side import** (side effect at the bottom of the file). Schedules
live in DB, admin-editable at `/admin`.
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable Major UI refresh on the nav-design-improvements branch. Drops 2013-era inline styles and consolidates patterns behind shared primitives. Foundation - New Wulf brand layer in app/styles/brand.css repointing --primary to the standards-guide blue (#0075AD) with utility classes for numerics (.num / .num-lg / .num-xl), metric labels, surface tints, and the wolf-mark watermark - Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font; Helvetica/Arial stays in the fallback chain for brand fidelity - Wordmark subtitle changed from "PSA Management System" to "Operations console" everywhere it appeared - Tagline footer ("Don't be afraid to cry") on every non-mobile page Status moved out of /dashboard - New /status route with integration tiles grouped by category, sync health table, worker pulse cards (analyzer / RMM / sync scheduler), token-expiry section, conditional alert banner - Top-bar StatusIndicator polls integration health every 60s and links to /status - INTEGRATIONS_DISABLED env var suppresses operator-disabled integrations (e.g. SentinelOne) — no failure noise from broken-on- purpose entries. Aliases supported (sentinelone → s1, etc.) Dashboard rebuilt around KPIs - /api/dashboard/overview adds today snapshot (opened, resolved, open total, SLA breaches) with delta math - /api/dashboard/trends backs queue × priority heatmap, 30-day volume area chart, 30-day mean resolution time line chart, today's active engineers leaderboard Components - StatusBadge driven by lib/status-registry.ts (priority, ticket status, classification, source, company type, publish, active / yes-no / billable / approved registries) - StatusLight (8px geometric square, five states, three sizes) - EmptyState (shared dashed panel with icon + headline + optional CTA) - KpiCard with delta indicator and tonal left border - WulfMark (mark / wordmark variants from /public/branding) - Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table) Navigation - Admin flat link → dropdown with seven shortcuts - New UserMenu (initials avatar, role badge, settings + sign-out) - Active-route highlight is now a 2px Wulf-blue underline echoing the PageHeader rule (consistent across flat links and submenu triggers); active children inside dropdowns use bg-primary/10 - Submenu width is content-driven (min-w 320 / max-w 440, single col) - Mobile hamburger via Sheet, reuses the same nav config Pages migrated - 16 admin sub-pages adopt PageHeader (with accent prop) - /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges - 10 raw <table> blocks across admin/sync/* migrated to shadcn Table - /veeam-analysis migrated to shadcn Table (kept its expansion logic) - Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs DataTable - Rewritten on @tanstack/react-table v8 in manual mode; external API unchanged so all 10+ data-browser pages keep working - New optional props for drill-down rows: getRowCanExpand + renderSubRow Mobile - Multi-select Popover gets max-w-[calc(100vw-1rem)] and collisionPadding so dropdowns can't overflow narrow viewports - CI filter bar wraps and shrinks; stat pill flows below Docs - New ARCHITECTURE.md (load-bearing reference for runtime, data flow, workers, analyzer pipeline, auth, deployment, gotchas) - New DESIGN.md (tokens, layout, navigation IA, component vocabulary, rolling backlog of remaining cleanup) - CLAUDE.md refreshed with pointers to the two new docs and the INTEGRATIONS_DISABLED operator config note - shadcn registry registered as project-level MCP server (.mcp.json) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
- Webhooks (`/api/webhooks/...`, `/api/zabbix/webhook`, `/api/rmm/loglift`) are
public per `middleware.ts`; they verify HMAC or a shared header themselves.
- Analyzer worker (`lib/services/analyzer/worker.ts`) and RMM Overshell worker
(`lib/services/rmm/worker.ts`) auto-start on import in production. Same
side-effect-import caveat as the sync scheduler.
## Auth
- Better Auth with magic link + TOTP 2FA + Microsoft OAuth. Roles: `user`, `admin`,
`super-admin`. Tables created in migration `012`.
- Default admin bootstrapped from `DEFAULT_ADMIN_EMAIL` via `lib/bootstrap.ts`.
- `middleware.ts` redirects unauth'd page requests to `/auth/sign-in`. Public
routes (webhooks, sync, health, mobile, openclaw, kiosk, legal, qbo callbacks)
are hardcoded there — add to that list when introducing a new public endpoint.
## Build / run / verify
- Dev: `npm run dev` → http://localhost:3100
- Build: `npm run build` (turbopack via Next 16)
- Type check: `npx tsc --noEmit --pretty`
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable Major UI refresh on the nav-design-improvements branch. Drops 2013-era inline styles and consolidates patterns behind shared primitives. Foundation - New Wulf brand layer in app/styles/brand.css repointing --primary to the standards-guide blue (#0075AD) with utility classes for numerics (.num / .num-lg / .num-xl), metric labels, surface tints, and the wolf-mark watermark - Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font; Helvetica/Arial stays in the fallback chain for brand fidelity - Wordmark subtitle changed from "PSA Management System" to "Operations console" everywhere it appeared - Tagline footer ("Don't be afraid to cry") on every non-mobile page Status moved out of /dashboard - New /status route with integration tiles grouped by category, sync health table, worker pulse cards (analyzer / RMM / sync scheduler), token-expiry section, conditional alert banner - Top-bar StatusIndicator polls integration health every 60s and links to /status - INTEGRATIONS_DISABLED env var suppresses operator-disabled integrations (e.g. SentinelOne) — no failure noise from broken-on- purpose entries. Aliases supported (sentinelone → s1, etc.) Dashboard rebuilt around KPIs - /api/dashboard/overview adds today snapshot (opened, resolved, open total, SLA breaches) with delta math - /api/dashboard/trends backs queue × priority heatmap, 30-day volume area chart, 30-day mean resolution time line chart, today's active engineers leaderboard Components - StatusBadge driven by lib/status-registry.ts (priority, ticket status, classification, source, company type, publish, active / yes-no / billable / approved registries) - StatusLight (8px geometric square, five states, three sizes) - EmptyState (shared dashed panel with icon + headline + optional CTA) - KpiCard with delta indicator and tonal left border - WulfMark (mark / wordmark variants from /public/branding) - Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table) Navigation - Admin flat link → dropdown with seven shortcuts - New UserMenu (initials avatar, role badge, settings + sign-out) - Active-route highlight is now a 2px Wulf-blue underline echoing the PageHeader rule (consistent across flat links and submenu triggers); active children inside dropdowns use bg-primary/10 - Submenu width is content-driven (min-w 320 / max-w 440, single col) - Mobile hamburger via Sheet, reuses the same nav config Pages migrated - 16 admin sub-pages adopt PageHeader (with accent prop) - /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges - 10 raw <table> blocks across admin/sync/* migrated to shadcn Table - /veeam-analysis migrated to shadcn Table (kept its expansion logic) - Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs DataTable - Rewritten on @tanstack/react-table v8 in manual mode; external API unchanged so all 10+ data-browser pages keep working - New optional props for drill-down rows: getRowCanExpand + renderSubRow Mobile - Multi-select Popover gets max-w-[calc(100vw-1rem)] and collisionPadding so dropdowns can't overflow narrow viewports - CI filter bar wraps and shrinks; stat pill flows below Docs - New ARCHITECTURE.md (load-bearing reference for runtime, data flow, workers, analyzer pipeline, auth, deployment, gotchas) - New DESIGN.md (tokens, layout, navigation IA, component vocabulary, rolling backlog of remaining cleanup) - CLAUDE.md refreshed with pointers to the two new docs and the INTEGRATIONS_DISABLED operator config note - shadcn registry registered as project-level MCP server (.mcp.json) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
- Tests: `npm test` (vitest) — covers `lib/services/analyzer/**`,
`lib/services/rmm/**`, `lib/services/b2/**`, and `lib/services/analyzer/
link-discovery.test.ts`. Other parts of the codebase have no tests — if you
touch them, type-check is the only safety net. No CI yet; tests are local-only.
- Docker: `docker compose up` from repo root. Postgres applies `migrations/*.sql`
on init only (existing volumes won't re-run them).
## Conventions to follow
- Files: kebab-case. Components: `PascalCase` exports from kebab-case files.
- Don't introduce ORMs, server actions, or alternative state libraries unless asked
— match the existing pattern.
- New SQL: numbered migration; never edit a committed one.
- Long-form per-feature documentation belongs in `docs/`. Don't duplicate it here.
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable Major UI refresh on the nav-design-improvements branch. Drops 2013-era inline styles and consolidates patterns behind shared primitives. Foundation - New Wulf brand layer in app/styles/brand.css repointing --primary to the standards-guide blue (#0075AD) with utility classes for numerics (.num / .num-lg / .num-xl), metric labels, surface tints, and the wolf-mark watermark - Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font; Helvetica/Arial stays in the fallback chain for brand fidelity - Wordmark subtitle changed from "PSA Management System" to "Operations console" everywhere it appeared - Tagline footer ("Don't be afraid to cry") on every non-mobile page Status moved out of /dashboard - New /status route with integration tiles grouped by category, sync health table, worker pulse cards (analyzer / RMM / sync scheduler), token-expiry section, conditional alert banner - Top-bar StatusIndicator polls integration health every 60s and links to /status - INTEGRATIONS_DISABLED env var suppresses operator-disabled integrations (e.g. SentinelOne) — no failure noise from broken-on- purpose entries. Aliases supported (sentinelone → s1, etc.) Dashboard rebuilt around KPIs - /api/dashboard/overview adds today snapshot (opened, resolved, open total, SLA breaches) with delta math - /api/dashboard/trends backs queue × priority heatmap, 30-day volume area chart, 30-day mean resolution time line chart, today's active engineers leaderboard Components - StatusBadge driven by lib/status-registry.ts (priority, ticket status, classification, source, company type, publish, active / yes-no / billable / approved registries) - StatusLight (8px geometric square, five states, three sizes) - EmptyState (shared dashed panel with icon + headline + optional CTA) - KpiCard with delta indicator and tonal left border - WulfMark (mark / wordmark variants from /public/branding) - Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table) Navigation - Admin flat link → dropdown with seven shortcuts - New UserMenu (initials avatar, role badge, settings + sign-out) - Active-route highlight is now a 2px Wulf-blue underline echoing the PageHeader rule (consistent across flat links and submenu triggers); active children inside dropdowns use bg-primary/10 - Submenu width is content-driven (min-w 320 / max-w 440, single col) - Mobile hamburger via Sheet, reuses the same nav config Pages migrated - 16 admin sub-pages adopt PageHeader (with accent prop) - /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges - 10 raw <table> blocks across admin/sync/* migrated to shadcn Table - /veeam-analysis migrated to shadcn Table (kept its expansion logic) - Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs DataTable - Rewritten on @tanstack/react-table v8 in manual mode; external API unchanged so all 10+ data-browser pages keep working - New optional props for drill-down rows: getRowCanExpand + renderSubRow Mobile - Multi-select Popover gets max-w-[calc(100vw-1rem)] and collisionPadding so dropdowns can't overflow narrow viewports - CI filter bar wraps and shrinks; stat pill flows below Docs - New ARCHITECTURE.md (load-bearing reference for runtime, data flow, workers, analyzer pipeline, auth, deployment, gotchas) - New DESIGN.md (tokens, layout, navigation IA, component vocabulary, rolling backlog of remaining cleanup) - CLAUDE.md refreshed with pointers to the two new docs and the INTEGRATIONS_DISABLED operator config note - shadcn registry registered as project-level MCP server (.mcp.json) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
## Operator config
feat(admin): DB-backed integration toggles + sticky cols + dark contrast Builds on the env-var INTEGRATIONS_DISABLED shipped with the nav-design overhaul. Adds a DB-backed admin UI so operators can flip integrations without editing .env and restarting the container, plus the remaining visual cleanup items from the design backlog. Integration toggles - Migration 081 — integration_settings table (key PK, disabled flag, reason, disabled_by audit, disabled_at). Seeded with all 13 known integrations as enabled. - GET / PATCH /api/admin/integrations — gated by requirePermission (admin, access). PATCH clears the in-process integration-health cache so toggles take effect within seconds. - /admin/integrations admin page with a Switch per integration, optional reason input, audit-info subtitle (disabled by, when, why), live status light from /api/dashboard/integration-health. - integration-health service merges env-var disable list with DB rows; degrades gracefully if migration unapplied / DB unreachable. - Wired into the Admin nav dropdown (eight items now). - CLAUDE.md describes both env + DB sources. Sticky first column on tables - Table primitive accepts stickyFirstColumn?: boolean. When true, TH and TD :first-child stay pinned during horizontal scroll, with background inheritance preserving hover and selected row tints. - DataTable exposes the prop too — on by default for paginated tables. - /addigy-devices opts in. Dark-mode contrast - --border lifted from 10% to 14% in .dark; --input from 15% to 18%; --sidebar-border to 14%. - StatusLight outline ring lifted from /10 to /15 (light) and /20 (dark). - DetailModal empty-cell em-dash lifted from /40 to /70 so missing values are legible on dark surfaces. DESIGN.md - Closed sticky-first-column, dark-mode contrast, and palette-audit items (palette deprioritized — most uses are semantic). - Skeleton helpers documented as preferred for new code; existing ad-hoc patterns left in place. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:55:22 -04:00
- **Integration disable** — two sources, merged:
- `INTEGRATIONS_DISABLED` env var (legacy / bootstrap fallback).
Comma- or space-separated keys with aliases (`sentinelone``s1`,
`datto``datto_rmm`, `it-glue``itglue`, `ms-graph``msgraph`).
Set in `.env` and restart.
- **`/admin/integrations`** UI backed by the `integration_settings`
table (migration 081). Toggle without a container restart; takes
effect within the 5-minute health cache (PATCH clears the cache
immediately). Audit columns capture `disabled_by` (session email),
`disabled_at`, and an optional `disabled_reason`.
In both cases live auth checks still run (logs surface the underlying
state); the UI ignores the result for disabled integrations.
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable Major UI refresh on the nav-design-improvements branch. Drops 2013-era inline styles and consolidates patterns behind shared primitives. Foundation - New Wulf brand layer in app/styles/brand.css repointing --primary to the standards-guide blue (#0075AD) with utility classes for numerics (.num / .num-lg / .num-xl), metric labels, surface tints, and the wolf-mark watermark - Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font; Helvetica/Arial stays in the fallback chain for brand fidelity - Wordmark subtitle changed from "PSA Management System" to "Operations console" everywhere it appeared - Tagline footer ("Don't be afraid to cry") on every non-mobile page Status moved out of /dashboard - New /status route with integration tiles grouped by category, sync health table, worker pulse cards (analyzer / RMM / sync scheduler), token-expiry section, conditional alert banner - Top-bar StatusIndicator polls integration health every 60s and links to /status - INTEGRATIONS_DISABLED env var suppresses operator-disabled integrations (e.g. SentinelOne) — no failure noise from broken-on- purpose entries. Aliases supported (sentinelone → s1, etc.) Dashboard rebuilt around KPIs - /api/dashboard/overview adds today snapshot (opened, resolved, open total, SLA breaches) with delta math - /api/dashboard/trends backs queue × priority heatmap, 30-day volume area chart, 30-day mean resolution time line chart, today's active engineers leaderboard Components - StatusBadge driven by lib/status-registry.ts (priority, ticket status, classification, source, company type, publish, active / yes-no / billable / approved registries) - StatusLight (8px geometric square, five states, three sizes) - EmptyState (shared dashed panel with icon + headline + optional CTA) - KpiCard with delta indicator and tonal left border - WulfMark (mark / wordmark variants from /public/branding) - Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table) Navigation - Admin flat link → dropdown with seven shortcuts - New UserMenu (initials avatar, role badge, settings + sign-out) - Active-route highlight is now a 2px Wulf-blue underline echoing the PageHeader rule (consistent across flat links and submenu triggers); active children inside dropdowns use bg-primary/10 - Submenu width is content-driven (min-w 320 / max-w 440, single col) - Mobile hamburger via Sheet, reuses the same nav config Pages migrated - 16 admin sub-pages adopt PageHeader (with accent prop) - /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges - 10 raw <table> blocks across admin/sync/* migrated to shadcn Table - /veeam-analysis migrated to shadcn Table (kept its expansion logic) - Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs DataTable - Rewritten on @tanstack/react-table v8 in manual mode; external API unchanged so all 10+ data-browser pages keep working - New optional props for drill-down rows: getRowCanExpand + renderSubRow Mobile - Multi-select Popover gets max-w-[calc(100vw-1rem)] and collisionPadding so dropdowns can't overflow narrow viewports - CI filter bar wraps and shrinks; stat pill flows below Docs - New ARCHITECTURE.md (load-bearing reference for runtime, data flow, workers, analyzer pipeline, auth, deployment, gotchas) - New DESIGN.md (tokens, layout, navigation IA, component vocabulary, rolling backlog of remaining cleanup) - CLAUDE.md refreshed with pointers to the two new docs and the INTEGRATIONS_DISABLED operator config note - shadcn registry registered as project-level MCP server (.mcp.json) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
## Watch out for
- A `.env` file is committed to the repo. Treat secrets as potentially real; don't
log/echo them, and flag this if it comes up.
- Duplicate migration numbers exist (002, 004, 009) — alphabetical apply order.
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable Major UI refresh on the nav-design-improvements branch. Drops 2013-era inline styles and consolidates patterns behind shared primitives. Foundation - New Wulf brand layer in app/styles/brand.css repointing --primary to the standards-guide blue (#0075AD) with utility classes for numerics (.num / .num-lg / .num-xl), metric labels, surface tints, and the wolf-mark watermark - Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font; Helvetica/Arial stays in the fallback chain for brand fidelity - Wordmark subtitle changed from "PSA Management System" to "Operations console" everywhere it appeared - Tagline footer ("Don't be afraid to cry") on every non-mobile page Status moved out of /dashboard - New /status route with integration tiles grouped by category, sync health table, worker pulse cards (analyzer / RMM / sync scheduler), token-expiry section, conditional alert banner - Top-bar StatusIndicator polls integration health every 60s and links to /status - INTEGRATIONS_DISABLED env var suppresses operator-disabled integrations (e.g. SentinelOne) — no failure noise from broken-on- purpose entries. Aliases supported (sentinelone → s1, etc.) Dashboard rebuilt around KPIs - /api/dashboard/overview adds today snapshot (opened, resolved, open total, SLA breaches) with delta math - /api/dashboard/trends backs queue × priority heatmap, 30-day volume area chart, 30-day mean resolution time line chart, today's active engineers leaderboard Components - StatusBadge driven by lib/status-registry.ts (priority, ticket status, classification, source, company type, publish, active / yes-no / billable / approved registries) - StatusLight (8px geometric square, five states, three sizes) - EmptyState (shared dashed panel with icon + headline + optional CTA) - KpiCard with delta indicator and tonal left border - WulfMark (mark / wordmark variants from /public/branding) - Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table) Navigation - Admin flat link → dropdown with seven shortcuts - New UserMenu (initials avatar, role badge, settings + sign-out) - Active-route highlight is now a 2px Wulf-blue underline echoing the PageHeader rule (consistent across flat links and submenu triggers); active children inside dropdowns use bg-primary/10 - Submenu width is content-driven (min-w 320 / max-w 440, single col) - Mobile hamburger via Sheet, reuses the same nav config Pages migrated - 16 admin sub-pages adopt PageHeader (with accent prop) - /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges - 10 raw <table> blocks across admin/sync/* migrated to shadcn Table - /veeam-analysis migrated to shadcn Table (kept its expansion logic) - Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs DataTable - Rewritten on @tanstack/react-table v8 in manual mode; external API unchanged so all 10+ data-browser pages keep working - New optional props for drill-down rows: getRowCanExpand + renderSubRow Mobile - Multi-select Popover gets max-w-[calc(100vw-1rem)] and collisionPadding so dropdowns can't overflow narrow viewports - CI filter bar wraps and shrinks; stat pill flows below Docs - New ARCHITECTURE.md (load-bearing reference for runtime, data flow, workers, analyzer pipeline, auth, deployment, gotchas) - New DESIGN.md (tokens, layout, navigation IA, component vocabulary, rolling backlog of remaining cleanup) - CLAUDE.md refreshed with pointers to the two new docs and the INTEGRATIONS_DISABLED operator config note - shadcn registry registered as project-level MCP server (.mcp.json) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
- Sync scheduler, analyzer worker, and RMM worker all auto-start as side effects
of being imported on the server. Don't eager-import them from hot paths or
shared utilities.
- Analyzer LLM provider is per-request (`anthropic` | `openrouter`). The
idempotency `content_hash` is provider-scoped — the same ticket can have one
Claude row and one OpenRouter row.
- Analyzer cost ceiling: Stage 4 (Opus) skipped above $2.00 estimated cost; the
analysis is flagged for human review.
- IT Glue results destined for an LLM **must** go through
`lib/services/analyzer/itglue-search.ts` (redacted). Don't pipe raw client
output into a prompt.
## Useful existing docs
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable Major UI refresh on the nav-design-improvements branch. Drops 2013-era inline styles and consolidates patterns behind shared primitives. Foundation - New Wulf brand layer in app/styles/brand.css repointing --primary to the standards-guide blue (#0075AD) with utility classes for numerics (.num / .num-lg / .num-xl), metric labels, surface tints, and the wolf-mark watermark - Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font; Helvetica/Arial stays in the fallback chain for brand fidelity - Wordmark subtitle changed from "PSA Management System" to "Operations console" everywhere it appeared - Tagline footer ("Don't be afraid to cry") on every non-mobile page Status moved out of /dashboard - New /status route with integration tiles grouped by category, sync health table, worker pulse cards (analyzer / RMM / sync scheduler), token-expiry section, conditional alert banner - Top-bar StatusIndicator polls integration health every 60s and links to /status - INTEGRATIONS_DISABLED env var suppresses operator-disabled integrations (e.g. SentinelOne) — no failure noise from broken-on- purpose entries. Aliases supported (sentinelone → s1, etc.) Dashboard rebuilt around KPIs - /api/dashboard/overview adds today snapshot (opened, resolved, open total, SLA breaches) with delta math - /api/dashboard/trends backs queue × priority heatmap, 30-day volume area chart, 30-day mean resolution time line chart, today's active engineers leaderboard Components - StatusBadge driven by lib/status-registry.ts (priority, ticket status, classification, source, company type, publish, active / yes-no / billable / approved registries) - StatusLight (8px geometric square, five states, three sizes) - EmptyState (shared dashed panel with icon + headline + optional CTA) - KpiCard with delta indicator and tonal left border - WulfMark (mark / wordmark variants from /public/branding) - Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table) Navigation - Admin flat link → dropdown with seven shortcuts - New UserMenu (initials avatar, role badge, settings + sign-out) - Active-route highlight is now a 2px Wulf-blue underline echoing the PageHeader rule (consistent across flat links and submenu triggers); active children inside dropdowns use bg-primary/10 - Submenu width is content-driven (min-w 320 / max-w 440, single col) - Mobile hamburger via Sheet, reuses the same nav config Pages migrated - 16 admin sub-pages adopt PageHeader (with accent prop) - /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges - 10 raw <table> blocks across admin/sync/* migrated to shadcn Table - /veeam-analysis migrated to shadcn Table (kept its expansion logic) - Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs DataTable - Rewritten on @tanstack/react-table v8 in manual mode; external API unchanged so all 10+ data-browser pages keep working - New optional props for drill-down rows: getRowCanExpand + renderSubRow Mobile - Multi-select Popover gets max-w-[calc(100vw-1rem)] and collisionPadding so dropdowns can't overflow narrow viewports - CI filter bar wraps and shrinks; stat pill flows below Docs - New ARCHITECTURE.md (load-bearing reference for runtime, data flow, workers, analyzer pipeline, auth, deployment, gotchas) - New DESIGN.md (tokens, layout, navigation IA, component vocabulary, rolling backlog of remaining cleanup) - CLAUDE.md refreshed with pointers to the two new docs and the INTEGRATIONS_DISABLED operator config note - shadcn registry registered as project-level MCP server (.mcp.json) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
- `ARCHITECTURE.md` — runtime, data flow, workers, analyzer pipeline (read first)
- `DESIGN.md` — UI tokens, nav IA, component conventions, current cleanup backlog
- `AUTOTASK_API_GUIDE.md`, `ADDIGY_API_GUIDE.md` — credential setup
- `POSTGRES_SYNC_SETUP.md`, `DOCKER_README.md`
- `PULSE_DATABASE_SKILL.md` — diagnostic queries
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable Major UI refresh on the nav-design-improvements branch. Drops 2013-era inline styles and consolidates patterns behind shared primitives. Foundation - New Wulf brand layer in app/styles/brand.css repointing --primary to the standards-guide blue (#0075AD) with utility classes for numerics (.num / .num-lg / .num-xl), metric labels, surface tints, and the wolf-mark watermark - Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font; Helvetica/Arial stays in the fallback chain for brand fidelity - Wordmark subtitle changed from "PSA Management System" to "Operations console" everywhere it appeared - Tagline footer ("Don't be afraid to cry") on every non-mobile page Status moved out of /dashboard - New /status route with integration tiles grouped by category, sync health table, worker pulse cards (analyzer / RMM / sync scheduler), token-expiry section, conditional alert banner - Top-bar StatusIndicator polls integration health every 60s and links to /status - INTEGRATIONS_DISABLED env var suppresses operator-disabled integrations (e.g. SentinelOne) — no failure noise from broken-on- purpose entries. Aliases supported (sentinelone → s1, etc.) Dashboard rebuilt around KPIs - /api/dashboard/overview adds today snapshot (opened, resolved, open total, SLA breaches) with delta math - /api/dashboard/trends backs queue × priority heatmap, 30-day volume area chart, 30-day mean resolution time line chart, today's active engineers leaderboard Components - StatusBadge driven by lib/status-registry.ts (priority, ticket status, classification, source, company type, publish, active / yes-no / billable / approved registries) - StatusLight (8px geometric square, five states, three sizes) - EmptyState (shared dashed panel with icon + headline + optional CTA) - KpiCard with delta indicator and tonal left border - WulfMark (mark / wordmark variants from /public/branding) - Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table) Navigation - Admin flat link → dropdown with seven shortcuts - New UserMenu (initials avatar, role badge, settings + sign-out) - Active-route highlight is now a 2px Wulf-blue underline echoing the PageHeader rule (consistent across flat links and submenu triggers); active children inside dropdowns use bg-primary/10 - Submenu width is content-driven (min-w 320 / max-w 440, single col) - Mobile hamburger via Sheet, reuses the same nav config Pages migrated - 16 admin sub-pages adopt PageHeader (with accent prop) - /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges - 10 raw <table> blocks across admin/sync/* migrated to shadcn Table - /veeam-analysis migrated to shadcn Table (kept its expansion logic) - Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs DataTable - Rewritten on @tanstack/react-table v8 in manual mode; external API unchanged so all 10+ data-browser pages keep working - New optional props for drill-down rows: getRowCanExpand + renderSubRow Mobile - Multi-select Popover gets max-w-[calc(100vw-1rem)] and collisionPadding so dropdowns can't overflow narrow viewports - CI filter bar wraps and shrinks; stat pill flows below Docs - New ARCHITECTURE.md (load-bearing reference for runtime, data flow, workers, analyzer pipeline, auth, deployment, gotchas) - New DESIGN.md (tokens, layout, navigation IA, component vocabulary, rolling backlog of remaining cleanup) - CLAUDE.md refreshed with pointers to the two new docs and the INTEGRATIONS_DISABLED operator config note - shadcn registry registered as project-level MCP server (.mcp.json) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
- `docs/` — sync behavior, webhook setup, workflow editor, analyzer runbook,
RMM Overshell + LogLift specs, IT Glue audit spec, per-integration guides
<!-- GSD:project-start source:PROJECT.md -->
## Project
**Pulse Mobile Shell Redesign**
A ground-up redesign of Pulse's `/mobile/*` shell — the manager-on-the-go view of
the existing Pulse PSA dashboard. It replaces the current mobile layout, swaps
the standalone `/mobile/nav` page for a Sheet drawer, restyles Dashboard /
Tickets / Finance, and adds two new mobile surfaces (Analyzer feed,
Engagement). Built on the existing Pulse codebase — same routes, same data,
phone-first layouts.
**Core Value:** A manager can open Pulse on their phone and, in under 30 seconds, see the
state of the business and triage tickets — without ever needing to switch to
desktop for read-only awareness.
### Constraints
- **Tech stack**: Next.js 16 App Router, React 19, Tailwind 4, shadcn/ui — match
existing Pulse conventions (no new state libraries, no SWR/react-query, no
ORM, no Zod in API routes unless required)
- **Routes**: Keep all existing `/mobile/*` paths. Replace files in place. No
new top-level routes outside `/mobile/`.
- **Build order**: Each spec step ships independently — no big-bang merge.
Phase boundaries should let each step land on `master` cleanly.
- **No service worker / no offline**: don't introduce `next-pwa` or a custom
SW in this iteration.
- **Auth**: existing Better Auth + middleware handles `/mobile/*`. No new
auth surface.
- **Data sources**: reuse existing endpoints where possible; add
`/api/mobile/*` only when an existing list endpoint doesn't return the
needed shape (e.g., analyzer feed).
<!-- GSD:project-end -->
<!-- GSD:stack-start source:codebase/STACK.md -->
## Technology Stack
## Languages
- TypeScript 5 - Entire codebase, strict mode enabled
- JavaScript/JSX - React components via TypeScript with JSX support
- SQL - PostgreSQL migrations and queries
- Bash - Build and deployment scripts
## Runtime
- Node.js (version inferred from package.json compatibility)
- Next.js 16.1.1 running on port 3100
- npm (lockfile: package-lock.json)
## Frameworks
- Next.js 16.1.1 - App Router with `output: 'standalone'` for Docker, React Compiler enabled
- React 19.2.3 - Server and client components, React Compiler active
- Better Auth 1.4.10 - Authentication with magic link, TOTP 2FA, Microsoft OAuth
- Tailwind CSS 4.1.18 - Utility-first styling
- shadcn/ui (via Radix UI primitives) - Component library: `components/ui/`
- Recharts 3.7.0 - Charts and graphs (analytics/dashboards)
- Lucide React 0.562.0 - Icon library
- Sonner 2.0.7 - Toast notifications
- cmdk 1.1.1 - Command palette component
- react-hook-form 7.70.0 - Form state management (admin/auth only)
- Zod 4.3.5 - Type-safe schema validation
- @hookform/resolvers 5.2.2 - Form resolver for Zod
- @tanstack/react-table 8.21.3 - Headless table library with sorting, pagination, search
- react-markdown 10.1.0 - Markdown rendering
- remark-gfm 4.0.1 - GitHub-flavored markdown support
- date-fns 4.1.0 - Date manipulation and formatting
- react-day-picker 9.13.0 - Calendar date picker
- clsx 2.1.1 - Conditional className utility
- tailwind-merge 3.4.0 - Merge Tailwind class conflicts
- class-variance-authority 0.7.1 - CSS-in-JS variant management
- next-themes 0.4.6 - Dark mode theme switching
## Testing & Build
- vitest 4.1.5 - Unit and integration testing runner
- Run: `npm test` (run once), `npm run test:watch` (watch mode)
- Test coverage for: `lib/services/analyzer/**`, `lib/services/rmm/**`, `lib/services/b2/**`, `lib/services/analyzer/link-discovery.test.ts`
- Turbopack (via Next.js 16) - Fast bundler for development and production
- ESLint 9.39.2 - Linting (with eslint-config-next 16.1.1)
- TypeScript - Type checking via `npx tsc --noEmit --pretty`
## Key Dependencies
- pg 8.11.0 - PostgreSQL client (no ORM); queries via `postgresClient` singleton
- ioredis 5.9.0 - Redis client for caching; optional (graceful fallback if REDIS_URL unset)
- node-cron 4.2.1 - Job scheduler for sync tasks and workflows
- @anthropic-ai/sdk 0.91.1 - Anthropic Claude API client for AI Ticket Analyzer pipeline
- Better Auth ecosystem packages - OAuth, 2FA, session management
- nodemailer 7.0.12 - Email delivery for magic link auth
- babel-plugin-react-compiler 1.0.0 - React Compiler for optimized renders
- shadcn 4.6.0 - CLI tool for adding shadcn/ui components
- baseline-browser-mapping 2.10.8 - Browser compatibility mapping
- tw-animate-css 1.4.0 - Tailwind animation utilities
## Configuration
- `BETTER_AUTH_URL` - Base URL for auth (e.g., `http://localhost:3100` or `https://pulse.wulfconsulting.cloud`)
- `BETTER_AUTH_SECRET` - Secret key for session signing
- `DATABASE_URL` or `POSTGRES_*` - PostgreSQL connection (host, port, db, user, password)
- `REDIS_URL` - Redis connection (e.g., `redis://localhost:6380` in Docker compose)
- `SESSION_TIMEOUT_SECONDS` - Session TTL (default: 86400 / 24 hours)
- `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`, `MICROSOFT_TENANT_ID` - Microsoft OAuth for login (tenant is specific, not 'common')
- `DEFAULT_ADMIN_EMAIL` - Bootstrap admin account email
- Integration env vars: prefixed by service (e.g., `AUTOTASK_*`, `DATTO_RMM_*`, `MSGRAPH_*`, etc.) — see INTEGRATIONS.md
- Path alias: `@/*` maps to project root for cleaner imports
- Target: ES2017
- Strict mode enabled
- Config: `tsconfig.json`
- File: `next.config.ts`
- Standalone output for Docker deployment
- React Compiler enabled
- Image domains: configurable (currently empty)
- `npm run build` → Next.js standalone app in `.next/`
- `npm run start` → Starts production server on port 3100
## Platform Requirements
- Node.js 18+ (inferred from Next.js 16 compatibility)
- npm 8+
- PostgreSQL 16 (local or Docker)
- Redis 7 (optional, enables caching)
- Docker & Docker Compose (for full stack)
- Docker with docker-compose.yml provided
- Postgres 16 container (applies migrations on init)
- Redis 7 container on port 6380 (custom, not 6379)
- Next.js app container on port 3100
- Traefik integration available (via labels in docker-compose.yml) for routing at `pulse.wulfconsulting.cloud`
- Container-based: standalone Next.js image with migrations applied via Postgres init
- Volume mounts for data persistence: `redis_data`, `postgres_data`
- Environment configuration via `.env.local` file mounted read-only
<!-- GSD:stack-end -->
<!-- GSD:conventions-start source:CONVENTIONS.md -->
## Conventions
## Naming Patterns
- kebab-case for all files and directories (e.g., `postgres-client.ts`, `invite-user-form.tsx`, `entity-sync.ts`)
- Nested directories use kebab-case (e.g., `lib/services/analyzer/`, `components/admin/users/`)
- camelCase for all functions (e.g., `getAutotaskClient()`, `transformCompany()`, `extractExplicitFromText()`)
- Factory functions prefixed with `get` (e.g., `getAutotaskClient()`, `getDattoRmmClient()`)
- Helper functions suffixed with descriptive intent (e.g., `relTime()`, `deriveSigningKey()`)
- Private/internal functions prefixed with underscore: `_INTERNALS` objects expose internals for test access
- camelCase for all variables (e.g., `isLoading`, `setData`, `ticketNumber`)
- Constants in UPPER_SNAKE_CASE (e.g., `MAX_EXPLICIT_LINKS`, `TICKET_NUMBER_REGEX`)
- Database column names are always snake_case (e.g., `company_name`, `is_active`, `created_at`)
- PascalCase for all type names (e.g., `ClassificationRule`, `WorkflowExecution`, `TicketData`)
- Single-letter generics are acceptable (e.g., `queryEntity<T>()`)
- Union types as literal strings (e.g., `type RuleType = 'branch_routing' | 'ticket_type'`)
- PascalCase exported from kebab-case files (e.g., export `InviteUserForm` from `invite-user-form.tsx`)
- Page components: `export default function ComponentName()` at end of file
- Form components: follow `[Resource]Form` naming (e.g., `InviteUserForm`, `SignInForm`, `UserForm`)
## Code Style
- TypeScript strict mode enabled (`"strict": true` in `tsconfig.json`)
- No explicit formatter config (ESLint handles style)
- Indentation: 2 spaces (inferred from existing code)
- ESLint: `eslint.config.mjs` with Next.js config (`eslint-config-next/core-web-vitals`, `eslint-config-next/typescript`)
- No additional custom rules beyond Next.js defaults
- Type checking: `npx tsc --noEmit --pretty` (must pass before commit)
## Import Organization
- Configured as `"@/*": ["./*"]` in `tsconfig.json`
- Use `@/lib/...`, `@/components/...`, `@/app/...` always
- Never use relative paths like `../../../` for imports
## Error Handling
- All async functions use `try/catch` blocks
- API routes: catch errors and return `NextResponse.json({ error, message }, { status })`
- Standard status codes: `500` for runtime errors, `503` for missing/bad config, `401`/`403` from auth helpers
- Error messages: include `error instanceof Error ? error.message : 'fallback message'`
## Logging
- `console.error()` for exceptions caught in try/catch (usually in API routes and services)
- Include context: `console.error('Failed to fetch companies:', error)`
- No `console.log()` for debugging (remove before commit per linter checks)
## Comments
- Explain *why*, not what (code shows the what)
- Non-obvious logic or business rules
- Performance-critical sections
- Workarounds or hacks (mark with `// HACK:` or `// NOTE:`)
- Used sparingly on complex functions
- Example from `lib/services/analyzer/link-discovery.ts`:
- Not required for simple getters/setters or obvious functions
## Function Design
- Keep functions focused: one responsibility per function
- Aim for <50 lines for page components, <30 for utilities
- Complex operations broken into smaller helpers
- Prefer object parameters for >3 arguments
- Don't use `any` — use specific types
- Use `Partial<T>` for optional object shapes
- Async functions always return `Promise<T>` explicitly
- Prefer `null` over `undefined` for missing values
- Use discriminated unions for success/error returns in critical paths (see analyzer pipeline)
## Module Design
- Prefer `export` at declaration point rather than grouped re-exports
- One main export per file (exception: barrel files in `components/ui/`)
- Internal utilities prefixed with underscore: `_INTERNALS` object for test access
- `components/ui/index.ts` exports all shadcn primitives
- `lib/types/` has domain-specific barrel files (e.g., `lib/types/workflow.ts`, `lib/types/autotask.ts`)
- Avoid deep nesting — import from files, not directories unless barrel exists
## Database Transformations
## Shared Components & Libraries
- shadcn primitives live in `components/ui/`
- Feature-specific components in sibling directories (e.g., `components/dashboard/`, `components/admin/`)
- Icons: Always use `lucide-react` (e.g., `import { Loader2, Send } from 'lucide-react'`)
- Use `@tanstack/react-table` via `components/admin/DataTable.tsx` wrapper
- Example: `<DataTable columns={columns} data={data} />`
- Use `components/admin/DetailModal.tsx` for entity details
- Follows card + tabs pattern (formatted/raw)
- Use `components/navigation/app-navigation.tsx` (`NavigationMenu` from Radix)
- Dropdowns prefer `@radix-ui/react-dropdown-menu` over submenus
- Use `sonner` library: `import { toast } from 'sonner'`
- Patterns: `toast.success()`, `toast.error()`, `toast.info()`
- Use `react-hook-form` + Zod for validation
- Only in admin/auth forms — NOT in every page
- Pattern: `useForm()` with `zodResolver()`, then `<Form>` wrapper from shadcn
- Use `recharts` for data visualization (e.g., `<BarChart>`, `<LineChart>`)
## What NOT to Introduce
- No ORMs (Prisma, TypeORM, etc.) — use `postgresClient` singleton and manual transforms
- No server actions (`'use server'`) — use API routes called via `fetch()` from clients
- No additional state libraries (SWR, react-query, TanStack Query) — match local `useState` + `fetch` pattern
- No change to authentication (Better Auth is final)
- No editing of committed migrations — always create new numbered ones
- Keeps codebase lean and explicit
- Reduces abstraction overhead
- Makes data flow (DB → API → Client) visible
## Migrations
<!-- GSD:conventions-end -->
<!-- GSD:architecture-start source:ARCHITECTURE.md -->
## Architecture
## Pattern Overview
- App Router pages (`'use client'`) fetch data via API routes using `fetch()`
- Three auto-starting background workers via side-effect imports (sync-scheduler, analyzer, RMM overshell)
- Service layer in `lib/services/` with factories + configuration helpers
- Postgres 16 as system of record (snake_case columns), Redis for caching only
- Manual transformation from snake_case DB columns to camelCase API responses
- Better Auth 1.4 for sessions + magic link + TOTP 2FA + Microsoft OAuth
## Layers
- Purpose: Accept HTTP requests, validate auth, delegate to services, return JSON responses
- Location: `app/api/*/route.ts`, `app/*/page.tsx`
- Contains: Next.js route handlers (GET/POST/PATCH/DELETE), page components
- Depends on: Auth via `lib/auth-utils.ts`, services via `lib/services/`
- Used by: Client-side fetch calls from UI components
- Purpose: Sync data from external APIs, run background jobs, execute workflows, store results in Postgres
- Location: `lib/services/*.ts` (~50 files) plus subdirectories (`analyzer/`, `rmm/`, `llm/`, `b2/`)
- Contains: Integration clients (autotask, datto-rmm, itglue, veeam, msgraph, etc.), sync logic, job executors, pipeline orchestration
- Depends on: Postgres client, external API clients, environment configuration
- Used by: API routes (sync endpoints, webhook handlers) and background workers
- Purpose: Query, upsert, and manage state in Postgres
- Location: `lib/services/postgres-client.ts` (singleton) + `migrations/NNN_*.sql`
- Contains: Connection pool, query builder methods (`query()`, `transaction()`, `upsert()`, `bulkUpsert()`), migration definitions
- Depends on: PostgreSQL 16 connection string from env
- Used by: All services that read/write data
- Purpose: Poll for work and execute sync/analysis/RMM tasks without blocking HTTP requests
- Location: `lib/services/sync-scheduler.ts`, `lib/services/analyzer/worker.ts`, `lib/services/rmm/worker.ts`
- Contains: node-cron scheduler, polling loops with exponential backoff, state machine handlers
- Depends on: Postgres client, service layer integrations
- Used by: Auto-start on module import (side effects); HTTP routes that need them running will import the module to start them
- Purpose: Render pages, dialogs, tables, charts, and handle client-side state
- Location: `components/ui/` (shadcn primitives), `components/*/` (feature-specific), `app/*/page.tsx`
- Contains: React components, hooks for fetch + state, sonner toasts, recharts visualizations
- Depends on: API routes via `fetch()`, client auth via Better Auth SDK
- Used by: Next.js pages and other components
- Purpose: Define TypeScript interfaces and database schema
- Location: `lib/types/<domain>.ts`, `migrations/NNN_*.sql`
- Contains: Entity types (ticket, company, analysis, rmm_execution, etc.), sync request shapes, API response envelopes
- Depends on: (none — they define contracts)
- Used by: Services, API routes, components
## Data Flow
- **HTTP requests:** Stateless; session from Better Auth cookie
- **Background jobs:** State in Postgres (status columns: `queued``in_flight``complete` / `failed`)
- **Caching:** Redis (optional, used for integration health checks, cache duration varies)
- **Authorization:** Checked in route handlers via `requireAuth()` / `requireAdmin()` / `requirePermission()` from `lib/auth-utils.ts`
## Key Abstractions
- Purpose: Lazy-load integration clients with configured credentials; provide `is<Name>Configured()` helper to check env vars
- Examples: `lib/services/autotask-factory.ts`, `lib/services/datto-rmm-factory.ts`, `lib/services/msgraph-factory.ts`
- Pattern: Export `getAutotaskClient()`, `getDattoRmmClient()`, `getMsgraphClient()` with caching; throw if credentials missing; bundled with `is<Name>Configured()` for upstream checks
- Why: Decouples client initialization from route handlers; allows conditional feature gates per env
- Purpose: Single connection pool for all Postgres queries; auto-lazy-initializes; provides ORM-like query builder
- Examples: `postgresClient.query()`, `postgresClient.transaction()`, `postgresClient.upsert()`, `postgresClient.bulkUpsert()`
- Pattern: Private constructor, static `getInstance()`, pool initialized on first use
- Why: Prevents connection leaks; provides consistent interface across ~50 services
- Purpose: Incremental/full sync of any entity type from external API → Postgres
- Examples: `lib/services/entity-sync.ts` (Autotask), `lib/services/itglue-sync-service.ts`, `lib/services/veeam-sync-service.ts`
- Pattern: Reads `lastTrackedModificationDateTime` from last_sync table; queries external API; batches upsert via `bulkUpsert()`
- Why: Codifies the "last sync timestamp + incremental pull + batch insert" pattern across integrations
- Purpose: Orchestrate multi-stage LLM analysis with fallbacks, cost guards, and persistence
- Examples: `lib/services/analyzer/pipeline.ts`, `lib/services/analyzer/stages/*.ts`
- Pattern: Each stage returns structured output (gap analysis, category, cost estimate); cost guard checks ceiling before Stage 4; all stages persisted to `analyzer_stage_executions`
- Why: Allows cost control (skip expensive Opus above $2.00), idempotency (content_hash), and debugging (inspect each stage's output)
- Purpose: Auto-start in production, poll for work, claim rows with `FOR UPDATE SKIP LOCKED`, execute, persist result
- Examples: `lib/services/analyzer/worker.ts`, `lib/services/rmm/worker.ts`, `lib/services/sync-scheduler.ts`
- Pattern: Side-effect import auto-starts on module load; exponential backoff if no work; row locking for multi-instance safety (analyzer), or single-instance gate (sync scheduler)
- Why: Keeps background work out of HTTP request path; analyzer is safe to scale (row locking); sync scheduler should run on one instance only
- Purpose: Define three roles (`user`, `admin`, `super-admin`) with per-resource permissions
- Examples: `lib/auth.ts` (auth config), `lib/permissions.ts` (permission matrix), `lib/auth-utils.ts` (runtime checks)
- Pattern: Route handler calls `requireAdmin()` / `requirePermission()`, which decode session and check role; middleware only verifies session cookie exists
- Why: Separates auth (middleware) from authorization (route handler); role-based gates are checked at the point of use
## Entry Points
- Location: `app/*/page.tsx`
- Triggers: Browser navigation to any route except public paths
- Responsibilities: Render page shell with `AppNavigation` + `PageHeader`, fetch data from API, render client-side components, handle toast/dialog interactions
- Location: `app/api/*/route.ts`
- Triggers: `fetch()` from client, external webhooks (Autotask, Zabbix, RMM), scheduler HTTP calls
- Responsibilities: Validate auth/webhook signature, delegate to service layer, return JSON response with appropriate status (200, 401, 403, 503, 500)
- Location: `app/api/webhooks/autotask`, `app/api/zabbix/webhook`, `app/api/rmm/loglift`
- Triggers: External systems (Autotask, Zabbix, OpenClaw) POST events
- Responsibilities: Verify HMAC or custom header, parse event, enqueue jobs or upsert data, return 200 (even on error so Autotask doesn't deactivate)
- Location: `app/api/sync/*`, `app/api/datto-rmm/sync`, `app/api/itglue/sync`, `app/api/veeam/sync`, etc.
- Triggers: `lib/services/sync-scheduler.ts` fires HTTP POST at configured times
- Responsibilities: Call sync service, update `last_sync` timestamp, return 200 on success or error message
- Location: `lib/services/sync-scheduler.ts` (cron), `lib/services/analyzer/worker.ts` (2s poll), `lib/services/rmm/worker.ts` (5s poll)
- Triggers: Auto-starts as side effect of module import; runs indefinitely in production
- Responsibilities: Poll for work from DB, claim row, execute, persist result, handle errors + logging
## Error Handling
- **Webhook handlers** — Return 200 even if processing fails; log error so ops can investigate via audit log
- **API route handlers** — `try/catch`, return `NextResponse.json({ error, message }, { status })` with conventions:
- **Analyzer pipeline** — Cost ceiling guard at $2.00; above that, Stage 4 (Opus) skipped, analysis flagged with `needs_review=true`
- **Sync services** — Incremental sync errors log + re-trigger on next schedule; full sync errors persist `last_error` to `sync_schedules` table
- **Background workers** — Stale jobs reset on worker boot (no recovery); exponential backoff on empty polls; errors logged with job ID for manual inspection
## Cross-Cutting Concerns
<!-- GSD:architecture-end -->
<!-- GSD:skills-start source:skills/ -->
## Project Skills
No project skills found. Add skills to any of: `.claude/skills/`, `.agents/skills/`, `.cursor/skills/`, or `.github/skills/` with a `SKILL.md` index file.
<!-- GSD:skills-end -->
<!-- GSD:workflow-start source:GSD defaults -->
## GSD Workflow Enforcement
Before using Edit, Write, or other file-changing tools, start work through a GSD command so planning artifacts and execution context stay in sync.
Use these entry points:
- `/gsd-quick` for small fixes, doc updates, and ad-hoc tasks
- `/gsd-debug` for investigation and bug fixing
- `/gsd-execute-phase` for planned phase work
Do not make direct repo edits outside a GSD workflow unless the user explicitly asks to bypass it.
<!-- GSD:workflow-end -->
<!-- GSD:profile-start -->
## Developer Profile
> Profile not yet configured. Run `/gsd-profile-user` to generate your developer profile.
> This section is managed by `generate-claude-profile` -- do not edit manually.
<!-- GSD:profile-end -->