wulf-pulse/CLAUDE.md
lorentz 9bfb57553d 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

8.6 KiB

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 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:
    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>_*
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.
  • 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 devhttp://localhost:3100
  • Build: npm run build (turbopack via Next 16)
  • Type check: npx tsc --noEmit --pretty
  • 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.

Operator config

  • INTEGRATIONS_DISABLED — comma- or space-separated list of integration keys (or aliases) to suppress from /status and the top-bar status light. Disabled entries render muted, don't count toward failure summaries, and don't flag the rollup. Set in .env and restart. Aliases: sentinelones1, dattodatto_rmm, it-glueitglue, ms-graphmsgraph. Live auth checks still run (so logs still surface the underlying state) but the UI ignores the result.

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.
  • 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

  • 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
  • docs/ — sync behavior, webhook setup, workflow editor, analyzer runbook, RMM Overshell + LogLift specs, IT Glue audit spec, per-integration guides