Files
resolutionflow/CLAUDE.md
Michael Chihlas bea34229d6
Some checks failed
Mirror to GitHub / mirror (push) Successful in 4s
CI / backend (pull_request) Failing after 18m54s
CI / frontend (pull_request) Failing after 47s
CI / e2e (pull_request) Has been skipped
chore: bump version and changelog (v0.1.0.0)
Add CW security roles reference docs and PSA ticket management plan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 14:44:03 +00:00

22 KiB

CLAUDE.md - Patherly / ResolutionFlow Project Context

Last Updated: April 16, 2026


Project Overview

Patherly (user-facing brand: ResolutionFlow) is a SaaS product for MSP professionals. It provides troubleshooting decision trees that guide engineers through proven troubleshooting paths, capture decisions and notes, and generate professional ticket documentation.

Target Market: MSP companies — IT service providers managing infrastructure and support for multiple clients.

SaaS Context: Multi-tenant design — teams represent MSP companies, trees shared within teams, tiered access (super_admin, team_admin, engineer, viewer).

Branding

Context Name Used
Repository / directory / database patherly (internal name)
Docker containers resolutionflow_postgres, resolutionflow_frontend, resolutionflow_backend
Backend, frontend UI, production URLs ResolutionFlow
  • Brand assets: brand-assets/ (source SVGs), frontend/src/assets/brand/ (app assets), frontend/public/icons/ (favicon)
  • Logo: 30px gradient square (ember orange) + "ResolutionFlow" in Bricolage Grotesque 700
  • Layout: Icon rail sidebar (72px default) with hover flyout panels. Pinnable to full 260px sidebar.
  • Terminology: User-facing label is "Flows" (not "Trees"). Procedural flows are called "Projects" in the UI. Step Library is called "Solutions Library" in the UI. tree_type column values unchanged in DB.
  • Reference mockups: docs/mockups/ (HTML files, open in browser)

Implementation Principles

  • Prefer correct architecture over minimal diff
  • If two approaches exist, implement the one that scales, not the one that's faster to write
  • Flag any "simpler approach" tradeoffs for product owner review before proceeding

Current State

  • Phase: Go-to-Market Validation (Pre-PMF)
  • Backend: Complete (55+ API endpoints, 100+ integration tests)
  • Frontend: Core features complete, Tree Editor functional
  • Database: PostgreSQL with Docker, 101 migrations
  • Detailed status: CURRENT-STATE.md

What's In Progress

  • GTM validation: Shadow & Ship — founder dogfooding for 2 weeks, then 5 colleague pilot
  • Solutions Library spec written (docs/plans/2026-03-23-solutions-library-design.md), implementation post-pilot
  • Remaining open issues: #66 Templates + Import/Export, #60 Recurring Issue Detection, #58 Step Feedback Flag

Tech Stack

Backend

Python FastAPI, PostgreSQL 16 (async SQLAlchemy 2.0 + asyncpg), Alembic, JWT (python-jose) + bcrypt, Pydantic v2, APScheduler 3.x

Frontend

React 19 + Vite + TypeScript, Tailwind CSS v4 (CSS-only config in index.css), Zustand (immer + zundo), React Router v7, Axios, Lucide React


Key Project Structure

patherly/
├── backend/app/
│   ├── main.py                     # FastAPI entry point
│   ├── api/endpoints/              # Route handlers
│   ├── api/deps.py                 # Auth dependencies
│   ├── core/                       # config, database, permissions, security, audit, rate_limit
│   ├── models/                     # SQLAlchemy models
│   ├── schemas/                    # Pydantic schemas
│   └── services/psa/               # PSA provider abstraction (connectwise/, autotask/, halopsa/)
├── backend/alembic/                # Migrations (001-070 sequential, then hash IDs)
├── backend/tests/                  # pytest integration tests
├── frontend/src/
│   ├── api/                        # Axios client + endpoint modules
│   ├── components/                 # UI components
│   ├── hooks/                      # usePermissions, useSessionTimer, etc.
│   ├── pages/                      # Page components
│   ├── store/                      # Zustand stores
│   └── types/                      # TypeScript interfaces
└── docs/plans/                     # Design docs & implementation plans

Environment Variables

Backend (backend/.env)

APP_NAME=ResolutionFlow
DEBUG=true
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/patherly
DATABASE_URL_SYNC=postgresql://postgres:postgres@localhost:5432/patherly
SECRET_KEY=<openssl rand -hex 32>
ACCESS_TOKEN_EXPIRE_MINUTES=5
REFRESH_TOKEN_EXPIRE_DAYS=7
REQUIRE_INVITE_CODE=true

Frontend (frontend/.env.local - optional)

VITE_API_URL=http://localhost:8000

ConnectWise PSA Integration

All reference materials in docs/connectwise/. See CONNECTWISE-API-REFERENCE.md first.

Best Practices Documentation

Read docs/connectwise/best-practices/ BEFORE implementing any CW API integration code:

  • PSA-API-Requests.md — HTTP methods, condition syntax, PATCH format. READ FIRST.
  • PSA-Callbacks.md — Callback matrix, HMAC verification.
  • PSA-Pagination.md — Forward-Only vs Navigable, Link headers.
  • PSA-Service-Tickets.md — Ticket field mappings.
  • PSA-Versioning.md — Pin application/vnd.connectwise.com+json; version=2025.16.
  • PSA-Cloud-URL-Formatting.md — Dynamic base URL via /login/companyinfo/{companyId}.
  • Bundled-Requests.md — Batch via /system/bundles.
  • PSA-Markdown.md — Notes support markdown.
  • PSA-Company-Synchronization.md — Filter companies by Status/Type.
  • PSA-Data-Protection.md — Request minimal permissions (MY not ALL).

Reference Files (read in this order)

  1. docs/connectwise/CONNECTWISE-API-REFERENCE.md — Auth patterns, endpoint map, field mappings.
  2. docs/connectwise/connectwise-psa-resolutionflow-reference.json — Extracted OpenAPI 3.0.1 spec (670 endpoints, 342 schemas).
  3. docs/connectwise/connectwise-psa-openapi-full.json — Full spec (1838 endpoints). Only if you need something outside the subset.

Key Implementation Rules

  • Auth: API Key auth (Base64 of companyId+publicKey:privateKey) + clientId header on every request
  • clientId is server-side config (CW_CLIENT_ID in config.py) — identifies ResolutionFlow app, NOT per-tenant. Per-connection: company_id, public_key, private_key, server_url
  • All PSA code in services/psa/PSAProvider abstract base, ConnectWiseProvider impl, PsaProviderRegistry for multi-PSA dispatch
  • PSA endpoints in api/endpoints/integrations.py — connection CRUD, ticket ops, member mapping
  • Credentials encrypted via services/psa/encryption.py (Fernet); stored per-team, never per-user
  • In-memory TTL cache in services/psa/cache.py for board/status/priority lookups
  • Integration flows: Session → Ticket Notes via POST /service/tickets/{id}/notes; Ticket Context → FlowPilot via ticket details/company/configs; Callbacks via /system/callbacks

Development Commands

# PostgreSQL (run from VPS SSH — docker not available in code-server, see Lesson 103)
docker start resolutionflow_postgres

# Backend (from backend/)
source venv/bin/activate
uvicorn app.main:app --reload

# Frontend (from frontend/) — requires Node 20 (use nvm: nvm use 20)
npm run dev

# Tests (from backend/)
pytest --override-ini="addopts="

# TypeScript check (use in code-server — avoids EACCES on dist/, see Lesson 105)
npx tsc -b

# Frontend build — stricter than tsc, always use as final check before push
cd frontend && npm run build

# Migrations
cd backend && alembic upgrade head
alembic revision --autogenerate -m "Description"  # do NOT pass --rev-id; Alembic generates hash IDs

# Access PostgreSQL (VPS SSH)
docker exec -it resolutionflow_postgres psql -U postgres -d resolutionflow

# CI runs on Gitea (NOT GitHub Actions): https://gitea.resolutionflow.com/chihlasm/resolutionflow/actions

URLs & Test Users

  • Frontend: http://localhost:5173 | Backend: http://localhost:8000 | API Docs: http://localhost:8000/api/docs
  • Test password: TestPass123! — users: admin@, teamadmin@, engineer@, pro@ (all @resolutionflow.example.com)

Critical Lessons Learned

Lessons 1-70 archived to docs/LESSONS-ARCHIVE.md — fixes are baked into the codebase.

71. Enhancement/branch_addition proposals cannot be directly approved: Backend returns 400 — requires modified_flow_data via "Edit & Publish". Only new_flow proposals support direct approve.

72. ai_sessions.status column is VARCHAR(30): Must fit requesting_escalation (23 chars). Verify length when adding new status values.

73. get_db rolls back on exception: Prevents InFailedSQLTransaction cascade. Never remove the await session.rollback() in the dependency.

74. FlowPilot action bar height chain: ViewTransitionOutlet wrapper needs flex flex-col. If action bar disappears, walk getBoundingClientRect() from app-shell down.

75. Dashboard prefill auto-submits: StartSessionInput passes { state: { prefill } }. Both FlowPilotSessionPage and AssistantChatPage auto-submit via useEffect + prefillHandledRef guard.

76. Active session navigation guard: FlowPilotSessionPage uses useBlocker to intercept navigation. "Pause & Leave" auto-pauses before proceeding.

77. Prefer manual Alembic migrations for targeted changes: --autogenerate picks up all table drift. For single-column fixes, use alembic revision -m "desc" and write op.alter_column() manually.

78. Landing page subtitle is "AI-Powered Troubleshooting for MSPs": Appears on login, register, and <title>. Not "Decision Tree Platform".

79. Custom modals must be mobile-responsive: Use items-end sm:items-center + max-w-full sm:max-w-lg. See Modal.tsx and PrepareSessionModal.tsx.

80. TopBar search collapses to icon on mobile: Full bar (hidden sm:block) + icon fallback (sm:hidden). Both open CommandPalette.

81. Never use transition: all in landing.css: Specify exact properties. transition: all animates layout and causes jank.

82. bun requires PATH setup: export BUN_INSTALL="$HOME/.bun" && export PATH="$BUN_INSTALL/bin:$PATH". Chromium deps: libatk1.0-0 libatk-bridge2.0-0 libcups2 libxkbcommon0 libatspi2.0-0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2.

84. AI session abandoned status is fully wired: POST /ai-sessions/{id}/abandon with optional reason. Frontend: aiSessionsApi.abandonSession()useFlowPilotSession().abandonSession().

85. Date range filter end dates must use end-of-day: Set toDate.setHours(23, 59, 59, 999). For string inputs append T23:59:59.999Z. See SessionHistoryPage.tsx.

86. Script Builder: /script-builderScriptBuilderSession model, script_builder_service.py, endpoints at /scripts/builder/. FlowPilot handoff via action_type: "open_script_builder" + sessionStorage context.

87. FlowPilot must ask GUI vs script preference: Ask BEFORE suggesting either approach. See FLOWPILOT_SYSTEM_PROMPT in flowpilot_engine.py.

88. Charcoal palette: Sidebar #0e1016, page #16181f, cards #1e2028, borders #2a2e3a. All via CSS variables in index.css @theme. Accent is electric blue (#60a5fa).

92. tsc -b in Dockerfile enforces noUnusedLocals/noUnusedParameters as hard errors. After refactors, trace every import and destructured prop. Check IDE yellow squiggles before pushing.

93. FlowPilot actions live in the page header, not a bottom bar: Resolve/Escalate/Share Update in header. Desktop: inline + overflow (Pause/Close). Mobile: single . Bottom = message input only.

94. Frontend chat uses unified_chat_service, not assistant_chat_service: AssistantChatPage/ai-sessions/{id}/chatunified_chat_service.py. Never wire chat into assistant_chat.py.

95. Image upload → AI vision: uploadsApi.upload()upload_ids in message → backend fetches S3 → storage_service.resize_image_for_vision() (Pillow, 1568px, PNG→JPEG) → base64 → Claude multimodal. Max 3 images/message. Images NOT stored in history.

96. bg-accent is electric blue — never use for code/kbd. Use bg-code for code blocks, bg-white/[0.12] for inline code/badges, bg-white/[0.08] for kbd.

97. Railway S3 provisioned: Bucket resolutionflow-uploads. Variables: STORAGE_ENDPOINT, STORAGE_ACCESS_KEY, STORAGE_SECRET_KEY, STORAGE_BUCKET_NAME, STORAGE_REGION. boto3 in storage_service.py.

98. lazyWithRetry for lazy routes: Use instead of React.lazy — auto-reloads on chunk failures with 10s sessionStorage debounce.

99. text-secondary renders invisible on dark backgrounds: Maps to --color-secondary (dark surface). Use text-muted-foreground (#848b9b) for readable secondary text. Never use text-muted for body text.

100. Hover pop-out card pattern: pointer-events-none on scrim (z-40), z-50 expanded card with own onClick, dismiss via onMouseLeave. Never put handlers on scrim.

101. AI marker format compliance: [QUESTIONS], [ACTIONS], [FORK] parsed by unified_chat_service.py. History stores display_content (stripped). Each user message gets [SYSTEM: ...] reminder appended in _call_anthropic_cached().

102. TaskLane activation must happen in ALL chat response paths: Three paths in AssistantChatPage.tsxhandleSend, sendPrefill, handleResumeNew. All must check response.actions/response.questions and call setShowTaskLane(true).

103. Docker not available in code-server: Use VPS SSH: docker exec resolutionflow_postgres psql -U postgres -d resolutionflow -t -c "SQL". Python also not available in container.

104. landing.css uses --lp-* variables: Never use var(--color-*) tokens in landing.css. Extend the --lp-* palette for new landing page colors.

105. npm run build fails with EACCES on dist/ in code-server: Use npx tsc -b to verify TypeScript without writing to dist/.

106. Guard async "select item → load data → apply state" flows: Use currentSelectionRef = useRef(id) — update on every switch, bail after each await if ref no longer matches. See AssistantChatPage.tsx currentChatRef.

107. Startup routines use _admin_session_factory(): RLS is enabled; get_db() at startup has no app.current_account_id, so queries return 0 rows. Affects lifespan, ensure_service_account, seed scripts.

108. Tables with no account_id (never add to RLS migrations): script_categories, platform_steps, template_trees, plan_feature_defaults, accounts. Scan at class level, not file level — one .py file can have multiple classes with different columns.

109. tree_shares.account_id must equal tree.account_id: Use tree owner's tenant, not the actor's. Cross-tenant admin shares become invisible after RLS enforcement.

110. Backfill account_id migrations require service-code audit: Grep all ModelClass( sites, verify account_id= is passed. SQLAlchemy accepts None silently; RLS WITH CHECK surfaces it at runtime as InsufficientPrivilegeError.

111. Global Axios interceptor fires before component .catch(): Fix optional-data endpoints at the source — return []/{} on provider failure instead of raising 502. See list_boards in integrations.py.

RBAC & Permissions

  • Role hierarchy: super_admin > team_admin > engineer > viewer
  • Team Admin: role='engineer' + is_team_admin=True + valid team_id
  • Backend deps: get_current_active_user, require_engineer_or_admin (blocks viewers), require_admin (super admin only)
  • Never use role == "admin" — use is_super_admin instead
  • Frontend: usePermissions() hook for all permission checks
  • Centralized: backend/app/core/permissions.py, frontend/src/hooks/usePermissions.ts

Design System

Source of truth: DESIGN-SYSTEM.md — always read before visual/UI decisions.

  • Theme: Flat, high-contrast dark (Sentry/PostHog-inspired). No glass morphism, no backdrop blur, no gradients on surfaces. Fonts: IBM Plex Sans (body), Bricolage Grotesque (headings), JetBrains Mono (code).
  • Backgrounds: bg-page (#16181f), bg-sidebar (#0e1016), bg-card (#1e2028), bg-elevated (#2a2d38)
  • Cards: bg-card + 1px border-default (#2a2e3a), 8px radius. Hover: border-hover (#3d4252)
  • Buttons: Primary: solid accent (#60a5fa / #2563eb), white text, 5px radius. Ghost: transparent + 1px border.
  • Inputs: bg-input (#252830) + 1px border-default, 5px radius. Focus: border-color: accent + box-shadow: 0 0 0 2px accent-dim
  • Text: text-headingtext-primarytext-muted-foreground (#848b9b). NEVER text-secondary — maps to a dark surface color.
  • Functional colors: #34d399 success, #fbbf24 warning, #f87171 danger, #67e8f9 info — each has -dim at 10% opacity
  • Deprecated: No glass-card, backdrop-filter: blur(), ambient orbs, ember orange (#f97316), or cyan as accent

Frontend Patterns

  • Component guidelines: Use cn() from @/lib/utils, Lucide icons (wrap in <span> for title), modals with fixed header/footer
  • Type organization: Create in types/, export from types/index.ts, import with import type { T } from '@/types'
  • Custom step flow: CustomStepModalPostStepActionModalContinuationModal. Use findCustomStep() not findNode() for custom step UUIDs.
  • Session sharing: ShareSessionModal + SharedSessionPage. Utils in lib/sessionShare.ts. Share URLs: /shared/sessions/:token.
  • Routing helper: Use getTreeNavigatePath() and getTreeEditorPath() from @/lib/routing for all tree/session navigation.
  • Account section: AccountLayout has NO sidebar nav. New account pages: route under account children in router.tsx + link card in AccountSettingsPage.
  • Dashboard cockpit: QuickStartPageStartSessionInput + PendingEscalations, ActiveFlowPilotSessions, RecentFlowPilotSessions. Collapsible section for PerformanceCards, KnowledgeBaseCards, TeamSummary.
  • Sidebar: Amber "New Session" → Home → RESOLVE → KNOWLEDGE (Flows, Scripts) → INSIGHTS. Footer: Account, Pin/Unpin.

Common Tasks

  • New endpoint: Create in endpoints/ → add to router.py → schema in schemas/ → tests → frontend API client
  • New page: Create in pages/ → route in router.tsx → nav link in AppLayout.tsx
  • New public route: Add at top level in router.tsx (alongside /login) — NOT inside ProtectedRoute/AppLayout
  • Schema change: Update model → alembic revision -m "desc" (no --rev-id) → review → alembic upgrade head
  • New frontend API module: Types in types/ → export from types/index.ts → client in api/ → export from api/index.ts

Coding Standards

Python

Type hints everywhere, async/await for DB, Pydantic validation, DateTime(timezone=True) always.

TypeScript

Interfaces for all data, const over let, functional components + hooks.

Git

  • Format: type: description (feat, fix, refactor, docs, test, chore)
  • Always include Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
  • Create feature branch BEFORE committing: git checkout -b feat/feature-name
  • Remote is Gitea: Push to gitea.resolutionflow.com/chihlasm/resolutionflow. Mirrors to GitHub via .gitea/workflows/mirror-to-github.yml — never push directly to GitHub.

After Completing Work

  1. Update CURRENT-STATE.md
  2. Update 03-DEVELOPMENT-ROADMAP.md
  3. Close related GitHub Issues: gh issue close #N
  4. Update CLAUDE.md if new patterns or lessons emerged

gstack (Browser & Workflow Skills)

Web browsing: Always use /browse. Never use mcp__claude-in-chrome__* tools.

Skills: /office-hours · /plan-ceo-review · /plan-eng-review · /plan-design-review · /design-consultation · /review (PR review) · /ship · /browse (headless QA) · /qa (QA + fix) · /qa-only · /design-review (visual QA) · /setup-browser-cookies · /retro · /investigate · /document-release · /codex · /careful · /freeze · /unfreeze · /guard · /gstack-upgrade


Deployment (Railway)

  • Production: resolutionflow.com (frontend), api.resolutionflow.com (backend)
  • Deploy pipeline: push to Gitea → mirrors to GitHub → Railway watches main
  • PR envs: need manual domain generation + VITE_API_URL with https:// prefix
  • ALLOW_RAILWAY_ORIGINS=true enables CORS for *.up.railway.app
  • Shared Variables auto-propagate to all PR envs — use for ANTHROPIC_API_KEY etc.
  • Super admin: backend/make_superadmin_simple.py list|<email>

Quick Reference

What Where
API Docs http://localhost:8000/api/docs
Detailed Status CURRENT-STATE.md
Development Roadmap 03-DEVELOPMENT-ROADMAP.md
GitHub Issues gh issue list --state open
Design System DESIGN-SYSTEM.md
Dev Environment DEV-ENV.md — VPS setup, Docker, CORS, networking

GitNexus — Code Intelligence

This project is indexed by GitNexus as resolutionflow. Use it selectively — for routine additive work (new endpoints, new components, isolated fixes) just read the files directly. GitNexus earns its cost when you're about to touch something genuinely central with many callers.

If any GitNexus tool warns the index is stale, run npx gitnexus analyze in terminal first.

When to Use It

Use GitNexus when:

  • Touching a core shared symbol with many callers — flowpilot_engine, unified_chat_service, auth middleware, get_db, shared hooks
  • Renaming anything used across multiple files
  • Tracing an unfamiliar bug through a call chain you haven't read
  • Assessing whether a refactor is safe before starting

Skip GitNexus when:

  • Adding a new endpoint, component, or isolated feature
  • Fixing a bug in a self-contained file
  • Making changes you can already see the full scope of by reading the file

Useful Tools

Tool When to use Command
query Find code by concept when you don't know where to look gitnexus_query({query: "auth validation"})
context See all callers/callees of a symbol before touching it gitnexus_context({name: "symbolName"})
impact Blast radius check before editing a shared symbol gitnexus_impact({target: "X", direction: "upstream"})
rename Safe multi-file rename gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})

Keeping the Index Fresh

A PostToolUse hook re-indexes automatically after git commit. To manually refresh:

npx gitnexus analyze