diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index a1494a6..905c3b8 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -128,21 +128,69 @@ Updated during roadmap creation. | Requirement | Phase | Status | |-------------|-------|--------| -| PWA-01 to PWA-04 | TBD | Pending | -| SHELL-01 to SHELL-06 | TBD | Pending | -| NAV-01 to NAV-03 | TBD | Pending | -| DRAWER-01 to DRAWER-06 | TBD | Pending | -| DASH-01 to DASH-04 | TBD | Pending | -| TICK-01 to TICK-07 | TBD | Pending | -| FIN-01 to FIN-02 | TBD | Pending | -| ANL-01 to ANL-06 | TBD | Pending | -| ENG-01 to ENG-09 | TBD | Pending | +| PWA-01 | Phase 1 | Pending | +| PWA-02 | Phase 1 | Pending | +| PWA-03 | Phase 1 | Pending | +| PWA-04 | Phase 1 | Pending | +| SHELL-01 | Phase 2 | Pending | +| SHELL-02 | Phase 2 | Pending | +| SHELL-03 | Phase 2 | Pending | +| SHELL-04 | Phase 2 | Pending | +| SHELL-05 | Phase 2 | Pending | +| SHELL-06 | Phase 2 | Pending | +| NAV-01 | Phase 2 | Pending | +| NAV-02 | Phase 2 | Pending | +| NAV-03 | Phase 2 | Pending | +| DRAWER-01 | Phase 2 | Pending | +| DRAWER-02 | Phase 2 | Pending | +| DRAWER-03 | Phase 2 | Pending | +| DRAWER-04 | Phase 2 | Pending | +| DRAWER-05 | Phase 2 | Pending | +| DRAWER-06 | Phase 2 | Pending | +| DASH-01 | Phase 3 | Pending | +| DASH-02 | Phase 3 | Pending | +| DASH-03 | Phase 3 | Pending | +| DASH-04 | Phase 3 | Pending | +| TICK-01 | Phase 4 | Pending | +| TICK-02 | Phase 4 | Pending | +| TICK-03 | Phase 4 | Pending | +| TICK-04 | Phase 4 | Pending | +| TICK-05 | Phase 4 | Pending | +| TICK-06 | Phase 4 | Pending | +| TICK-07 | Phase 4 | Pending | +| FIN-01 | Phase 5 | Pending | +| FIN-02 | Phase 5 | Pending | +| ANL-01 | Phase 6 | Pending | +| ANL-02 | Phase 6 | Pending | +| ANL-03 | Phase 6 | Pending | +| ANL-04 | Phase 6 | Pending | +| ANL-05 | Phase 6 | Pending | +| ANL-06 | Phase 6 | Pending | +| ENG-01 | Phase 7 | Pending | +| ENG-02 | Phase 7 | Pending | +| ENG-03 | Phase 7 | Pending | +| ENG-04 | Phase 7 | Pending | +| ENG-05 | Phase 7 | Pending | +| ENG-09 | Phase 7 | Pending | +| ENG-06 | Phase 8 | Pending | +| ENG-07 | Phase 8 | Pending | +| ENG-08 | Phase 8 | Pending | **Coverage:** - v1 requirements: 47 total -- Mapped to phases: 0 (pending roadmap) -- Unmapped: 47 ⚠️ (resolved by roadmapper) +- Mapped to phases: 47 +- Unmapped: 0 ✓ + +**Per-phase counts:** +- Phase 1 (PWA Scaffolding): 4 requirements +- Phase 2 (Mobile Shell + More Drawer): 15 requirements +- Phase 3 (Dashboard Restyle): 4 requirements +- Phase 4 (Tickets Restyle): 7 requirements +- Phase 5 (Finance Restyle): 2 requirements +- Phase 6 (Analyzer Feed): 6 requirements +- Phase 7 (Engagement Overview): 6 requirements +- Phase 8 (Engagement User Profile): 3 requirements --- *Requirements defined: 2026-05-03* -*Last updated: 2026-05-03 after initial definition* +*Last updated: 2026-05-03 — traceability filled in at roadmap creation* diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md new file mode 100644 index 0000000..bffa497 --- /dev/null +++ b/.planning/ROADMAP.md @@ -0,0 +1,151 @@ +# Roadmap: Pulse Mobile Shell Redesign + +## Overview + +Eight phases mirror the deliberate build order in the source spec +(`docs/superpowers/specs/2026-05-03-mobile-shell-design.md` §8). Each phase +ships independently to `master` — no big-bang merge. Phase 1 lays PWA +metadata and safe-area utilities. Phase 2 rebuilds `app/mobile/layout.tsx` +with the new header, 5-cell bottom nav, and More drawer (deleting +`/mobile/nav` in the same change). Once the shell lands, Phases 3–7 are +independent restyles/new pages and may be executed in parallel; Phase 8 +follows Phase 7 because the user profile is reached from the Engagement +overview. All work happens in place under `/mobile/*` — no `/mobile-v2`, +no parallel routes. + +## Phases + +**Phase Numbering:** +- Integer phases (1, 2, 3): Planned milestone work +- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED) + +Decimal phases appear between their surrounding integers in numeric order. + +- [ ] **Phase 1: PWA Scaffolding** — Manifest, viewport meta, and safe-area utilities so the shell installs and paints under the home indicator +- [ ] **Phase 2: Mobile Shell + More Drawer** — New `app/mobile/layout.tsx` (header + 5-cell bottom nav) and Sheet drawer that replaces `/mobile/nav` +- [ ] **Phase 3: Dashboard Restyle** — 2×2 KPI grid, Needs Attention strip, worker/backup status row (no charts) +- [ ] **Phase 4: Tickets Restyle** — Collapsible URL-synced filters, priority-bar rows, cursor-based infinite scroll, detail header reskin +- [ ] **Phase 5: Finance Restyle** — Adopt new Card + typography scale, swap wide tables for stacked lists +- [ ] **Phase 6: Analyzer Feed (NEW)** — `/mobile/analyzer` read-only stream + `/api/mobile/analyzer/feed` +- [ ] **Phase 7: Engagement Overview (NEW)** — `/mobile/engagement` phone-first overview reachable from the More drawer +- [ ] **Phase 8: Engagement User Profile (NEW)** — `/mobile/engagement/[userId]` real-page profile that replaces the desktop modal pattern + +## Phase Details + +### Phase 1: PWA Scaffolding +**Goal**: A manager who taps "Add to Home Screen" gets a standalone Pulse icon that opens to the mobile shell with content respecting the device safe areas. +**Depends on**: Nothing (first phase) +**Requirements**: PWA-01, PWA-02, PWA-03, PWA-04 +**Success Criteria** (what must be TRUE): + 1. Visiting `/manifest.json` returns valid JSON with `name: "Pulse"`, `display: "standalone"`, `start_url: "/mobile"`, and theme/background colors matching the app shells + 2. The root `app/layout.tsx` references the manifest via `` and the viewport meta includes `viewport-fit=cover` + 3. A safe-area utility (Tailwind arbitrary values or shared class) is available so any sticky top/bottom bar can opt into `env(safe-area-inset-top)` / `env(safe-area-inset-bottom)` padding + 4. Installing Pulse to a phone home screen launches a chromeless app pointed at `/mobile` (no service worker, no offline) +**Plans**: TBD +**UI hint**: no + +### Phase 2: Mobile Shell + More Drawer +**Goal**: Every `/mobile/*` page renders inside a new layout — sticky header (Wulf mark + Bell placeholder + avatar), scrollable content, and a 5-cell bottom nav whose fifth control opens a Sheet drawer that fully replaces `/mobile/nav`. +**Depends on**: Phase 1 +**Requirements**: SHELL-01, SHELL-02, SHELL-03, SHELL-04, SHELL-05, SHELL-06, NAV-01, NAV-02, NAV-03, DRAWER-01, DRAWER-02, DRAWER-03, DRAWER-04, DRAWER-05, DRAWER-06 +**Success Criteria** (what must be TRUE): + 1. On any `/mobile/*` route the user sees a sticky header with the Wulf wordmark linking to `/mobile/dashboard`, a Bell icon button (keyboard-focusable, no menu), and a compact avatar — no page title in the header + 2. A fixed bottom bar exposes four primary tabs (Dashboard, Tickets, Finance, Analyzer) plus a More cell; tapping a tab routes to its page and the active tab uses `text-primary` based on `pathname.startsWith(href)` + 3. Tapping More (or the header avatar) opens a single Sheet drawer with three sections — Mobile sections (Engagement), Full site (Quotes, Configuration Items, Backup Status, Ticket Digest, Admin/Sync — each with an `ExternalLink` hint), and Account (current user read-only + Sign out) + 4. Tapping Sign out in the drawer signs the user out and lands them on `/auth/sign-in` + 5. `app/mobile/nav/page.tsx` no longer exists; visiting `/mobile/nav` does not render the old standalone nav page + 6. Page content scrolls under the sticky header and is not hidden behind the bottom nav (bottom padding accounts for nav height + safe-area inset) +**Plans**: TBD +**UI hint**: yes + +### Phase 3: Dashboard Restyle +**Goal**: A manager opening `/mobile/dashboard` sees the state of the business at a glance — four KPIs, items needing attention, and a worker/backup status row — with no charts. +**Depends on**: Phase 2 +**Requirements**: DASH-01, DASH-02, DASH-03, DASH-04 +**Success Criteria** (what must be TRUE): + 1. Dashboard renders a 2×2 grid of four primary KPI cards drawn from desktop hero stats (no 1×4 row, no charts) + 2. Below the grid, a "Needs Attention" horizontally-scrollable strip surfaces overdue tickets, failed backups, and stalled workflows; tapping a card opens its detail view + 3. A compact status row shows analyzer worker, RMM worker, and backup-success-rate; tapping any element opens the corresponding desktop admin page + 4. The page contains no recharts/chart components on phone widths +**Plans**: TBD +**UI hint**: yes + +### Phase 4: Tickets Restyle +**Goal**: A manager triages tickets on a phone with a collapsible filter bar that deep-links via URL, priority-coloured rows, and infinite scroll — and the detail page header matches the new shell. +**Depends on**: Phase 2 +**Requirements**: TICK-01, TICK-02, TICK-03, TICK-04, TICK-05, TICK-06, TICK-07 +**Success Criteria** (what must be TRUE): + 1. The Tickets page opens with the filter strip collapsed; expanding it reveals status, priority, queue, and an assigned-to-me toggle, and changing any filter updates the URL query string (deep link works on reload) + 2. Each list row has a left-edge stripe matching priority (Critical/High/Medium/Low → red/orange/amber/slate) and shows ticket #, title, company, age, and assignee + 3. Single-tapping a row navigates to `/mobile/tickets/[id]` + 4. Scrolling to the bottom of the list automatically loads the next ~25 rows (no Next button); a "Load more" fallback button is also visible/focusable for accessibility + 5. The detail page header uses the new shell styling (Wulf mark, breadcrumb back) while the body remains largely unchanged +**Plans**: TBD +**UI hint**: yes + +### Phase 5: Finance Restyle +**Goal**: A manager reading AR / invoice / payment status on a phone sees properly spaced cards and stacked lists instead of squished wide tables — same data, new shell. +**Depends on**: Phase 2 +**Requirements**: FIN-01, FIN-02 +**Success Criteria** (what must be TRUE): + 1. `/mobile/finance` adopts the new Card and typography scale — no horizontal overflow, spacing legible on small phones + 2. Sections that previously rendered wide tables on phone widths now render as stacked lists (no new sections, no new data sources) +**Plans**: TBD +**UI hint**: yes + +### Phase 6: Analyzer Feed (NEW) +**Goal**: A manager taps the Analyzer tab and skims a most-recent-first stream of AI ticket analyses, opening any one to a phone-friendly summary view that links out to desktop for full details. +**Depends on**: Phase 2 +**Requirements**: ANL-01, ANL-02, ANL-03, ANL-04, ANL-05, ANL-06 +**Success Criteria** (what must be TRUE): + 1. Tapping the Analyzer tab in the bottom nav lands on `/mobile/analyzer` and shows a most-recent-first list of AI ticket analyses + 2. Each row shows ticket #, title, the analyzer's one-line summary, a confidence badge, and a stage indicator (Triage → Analyze → Deep Review) + 3. Tapping a row opens a mobile summary view rendering Summary, Next Step, and Next Step Rationale, with a "View full analysis" link out to the desktop analyzer page + 4. The mobile feed never exposes editing, re-run, or prompt-tuning controls (read-only by design) + 5. The list reads from `analyzer_analyses` via `/api/mobile/analyzer/feed` (or a reused list endpoint that already returns the right shape) +**Plans**: TBD +**UI hint**: yes + +### Phase 7: Engagement Overview (NEW) +**Goal**: A manager reaches Engagement from the More drawer and sees a phone-first overview — period chips, stacked summary cards, a sortable per-employee list, and one compact sparkline. +**Depends on**: Phase 2 +**Requirements**: ENG-01, ENG-02, ENG-03, ENG-04, ENG-05, ENG-09 +**Success Criteria** (what must be TRUE): + 1. The Mobile sections row in the More drawer links to `/mobile/engagement`; the Analyzer is on the bottom bar but Engagement is not + 2. The overview page shows a period selector (today / 7d / 30d) sticky just below the H1, with active period clearly indicated + 3. Summary cards (active users, total Graph hours, total Autotask hours, hours-per-active-user) render single-column stacked — no 4-up grid on phone widths + 4. The per-employee list renders as stacked rows (avatar/initials, name, role, hours bar) with a search input and a sort control above (sort by hours, name, utilization) + 5. A single compact "hours trend" sparkline renders at the top of the list, scoped to the selected period — no multi-series chart +**Plans**: TBD +**UI hint**: yes + +### Phase 8: Engagement User Profile (NEW) +**Goal**: From the Engagement overview, a manager taps an employee row and arrives at a real, shareable profile page — single-column phone-first — and the device back gesture returns them to the overview. +**Depends on**: Phase 7 +**Requirements**: ENG-06, ENG-07, ENG-08 +**Success Criteria** (what must be TRUE): + 1. Tapping a row in the per-employee list navigates to `/mobile/engagement/[userId]` (segment form, shareable URL) + 2. The profile is a real page (not a modal) — the device/browser back gesture returns to the overview at the same scroll position + 3. The profile renders single-column: identity header → period selector → key metrics (compact) → activity breakdown list → recent items, sourced from the existing engagement profile data endpoints (no new data) +**Plans**: TBD +**UI hint**: yes + +## Progress + +**Execution Order:** +Phases execute in numeric order. Phase 2 unblocks Phases 3–7 (any order, parallelizable). Phase 8 follows Phase 7. + +| Phase | Plans Complete | Status | Completed | +|-------|----------------|--------|-----------| +| 1. PWA Scaffolding | 0/TBD | Not started | - | +| 2. Mobile Shell + More Drawer | 0/TBD | Not started | - | +| 3. Dashboard Restyle | 0/TBD | Not started | - | +| 4. Tickets Restyle | 0/TBD | Not started | - | +| 5. Finance Restyle | 0/TBD | Not started | - | +| 6. Analyzer Feed | 0/TBD | Not started | - | +| 7. Engagement Overview | 0/TBD | Not started | - | +| 8. Engagement User Profile | 0/TBD | Not started | - | + +--- +*Roadmap created: 2026-05-03* +*Source spec: `docs/superpowers/specs/2026-05-03-mobile-shell-design.md`* diff --git a/.planning/STATE.md b/.planning/STATE.md new file mode 100644 index 0000000..f4a7146 --- /dev/null +++ b/.planning/STATE.md @@ -0,0 +1,61 @@ +# Project State + +## Project Reference + +See: .planning/PROJECT.md (updated 2026-05-03) + +**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. +**Current focus:** Phase 1 — PWA Scaffolding + +## Current Position + +Phase: 1 of 8 (PWA Scaffolding) +Plan: 0 of TBD in current phase +Status: Ready to plan +Last activity: 2026-05-03 — Roadmap created (8 phases, 47/47 v1 requirements mapped) + +Progress: [░░░░░░░░░░] 0% + +## Performance Metrics + +**Velocity:** +- Total plans completed: 0 +- Average duration: — +- Total execution time: 0.0 hours + +**By Phase:** + +| Phase | Plans | Total | Avg/Plan | +|-------|-------|-------|----------| +| - | - | - | - | + +**Recent Trend:** +- Last 5 plans: — +- Trend: — + +*Updated after each plan completion* + +## Accumulated Context + +### Decisions + +Decisions are logged in PROJECT.md Key Decisions table. +Recent decisions affecting current work: + +- Roadmap: Phases mirror the spec's 8-step build order so each step ships independently to `master` (spec §8) +- Phase 2 unblocks Phases 3–7; Phases 3–7 are mutually independent and can be parallelized; Phase 8 depends on Phase 7 +- All work happens in place under `/mobile/*` — no `/mobile-v2`, no parallel routes (spec §2) + +### Pending Todos + +None yet. + +### Blockers/Concerns + +None yet. + +## Session Continuity + +Last session: 2026-05-03 +Stopped at: Roadmap created — ready to plan Phase 1 +Resume file: None diff --git a/CLAUDE.md b/CLAUDE.md index 3c917a5..54d2bb3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -161,3 +161,339 @@ Examples: `getAutotaskClient()`, `getMsgraphClient()`, `getDattoRmmClient()`, - `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 + + +## 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). + + + +## 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 + + + +## 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()`) +- 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` for optional object shapes +- Async functions always return `Promise` 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: `` +- 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 `
` wrapper from shadcn +- Use `recharts` for data visualization (e.g., ``, ``) +## 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 + + + +## 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/.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 `isConfigured()` 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 `isConfigured()` 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 + + + +## 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 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. + + + +## 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. +