docs: update CLAUDE.md — add ConnectWise PSA integration section and formatting fixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
69
CLAUDE.md
69
CLAUDE.md
@@ -30,6 +30,7 @@
|
|||||||
- **Rebrand guide:** [REBRAND-IMPLEMENTATION-GUIDE.md](REBRAND-IMPLEMENTATION-GUIDE.md)
|
- **Rebrand guide:** [REBRAND-IMPLEMENTATION-GUIDE.md](REBRAND-IMPLEMENTATION-GUIDE.md)
|
||||||
|
|
||||||
**Component styling rules:**
|
**Component styling rules:**
|
||||||
|
|
||||||
- Primary buttons: `bg-gradient-brand` (cyan `135deg`) with `shadow-lg shadow-primary/20`, hover `opacity-0.9`, active `scale(0.97)`
|
- Primary buttons: `bg-gradient-brand` (cyan `135deg`) with `shadow-lg shadow-primary/20`, hover `opacity-0.9`, active `scale(0.97)`
|
||||||
- Secondary buttons: `bg-[rgba(255,255,255,0.04)]` with `border-[rgba(255,255,255,0.06)]`, hover brightens border
|
- Secondary buttons: `bg-[rgba(255,255,255,0.04)]` with `border-[rgba(255,255,255,0.06)]`, hover brightens border
|
||||||
- Active nav items: `bg-primary/10` background + 3px left cyan gradient accent bar
|
- Active nav items: `bg-primary/10` background + 3px left cyan gradient accent bar
|
||||||
@@ -47,6 +48,7 @@ When adding new pages/components: use "ResolutionFlow" branding, ice-cyan gradie
|
|||||||
- Prefer correct architecture over minimal diff
|
- Prefer correct architecture over minimal diff
|
||||||
- If two approaches exist, implement the one that scales, not the one that's faster to write
|
- 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
|
- Flag any "simpler approach" tradeoffs for product owner review before proceeding
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Current State
|
## Current State
|
||||||
@@ -77,6 +79,7 @@ When adding new pages/components: use "ResolutionFlow" branding, ice-cyan gradie
|
|||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
### Backend
|
### Backend
|
||||||
|
|
||||||
- **Framework:** Python FastAPI
|
- **Framework:** Python FastAPI
|
||||||
- **Database:** PostgreSQL 16 (async via SQLAlchemy 2.0 + asyncpg)
|
- **Database:** PostgreSQL 16 (async via SQLAlchemy 2.0 + asyncpg)
|
||||||
- **Migrations:** Alembic
|
- **Migrations:** Alembic
|
||||||
@@ -85,6 +88,7 @@ When adding new pages/components: use "ResolutionFlow" branding, ice-cyan gradie
|
|||||||
- **Scheduling:** APScheduler 3.x (async, in-process with FastAPI lifespan) + croniter + pytz
|
- **Scheduling:** APScheduler 3.x (async, in-process with FastAPI lifespan) + croniter + pytz
|
||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
|
|
||||||
- **Framework:** React 19 + Vite + TypeScript
|
- **Framework:** React 19 + Vite + TypeScript
|
||||||
- **Styling:** Tailwind CSS v3 — dark-first with purple gradient accents (see Branding section)
|
- **Styling:** Tailwind CSS v3 — dark-first with purple gradient accents (see Branding section)
|
||||||
- **State:** Zustand (with immer + zundo for undo/redo)
|
- **State:** Zustand (with immer + zundo for undo/redo)
|
||||||
@@ -131,6 +135,7 @@ patherly/
|
|||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
### Backend (`backend/.env`)
|
### Backend (`backend/.env`)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
APP_NAME=ResolutionFlow
|
APP_NAME=ResolutionFlow
|
||||||
DEBUG=true
|
DEBUG=true
|
||||||
@@ -143,12 +148,54 @@ REQUIRE_INVITE_CODE=true
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Frontend (`frontend/.env.local` - optional)
|
### Frontend (`frontend/.env.local` - optional)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
VITE_API_URL=http://localhost:8000
|
VITE_API_URL=http://localhost:8000
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## ConnectWise PSA Integration
|
||||||
|
|
||||||
|
ResolutionFlow integrates with ConnectWise PSA (formerly Manage) as the primary PSA integration. All ConnectWise API reference materials live in `docs/connectwise/`.
|
||||||
|
|
||||||
|
### Best Practices Documentation
|
||||||
|
|
||||||
|
Official ConnectWise developer guides live in `docs/connectwise/best-practices/`. Read these BEFORE implementing any CW API integration code:
|
||||||
|
|
||||||
|
- `PSA-API-Requests.md` — HTTP methods, response codes, condition query syntax, PATCH format, URL encoding, partial responses, custom fields. READ FIRST.
|
||||||
|
- `PSA-Callbacks.md` — Callback type/level matrix, retry behavior, URL parameter gotcha, HMAC signature verification.
|
||||||
|
- `PSA-Pagination.md` — Navigable vs Forward-Only pagination, Link headers, while-loop pattern.
|
||||||
|
- `PSA-Service-Tickets.md` — Ticket field philosophy, recommended field mappings.
|
||||||
|
- `PSA-Versioning.md` — Pin API version via Accept header. Use `application/vnd.connectwise.com+json; version=2025.16`.
|
||||||
|
- `PSA-Cloud-URL-Formatting.md` — Dynamic base URL construction via `/login/companyinfo/{companyId}`.
|
||||||
|
- `Bundled-Requests.md` — Batch multiple API calls into one request via `/system/bundles`.
|
||||||
|
- `PSA-Markdown.md` — Ticket notes support markdown. Format session documentation output accordingly.
|
||||||
|
- `PSA-Company-Synchronization.md` — Filter companies by Status/Type for mapping UI.
|
||||||
|
- `PSA-Data-Protection.md` — Security role model, request minimal permissions (MY not ALL).
|
||||||
|
|
||||||
|
### Reference Files (read in this order)
|
||||||
|
|
||||||
|
1. `docs/connectwise/CONNECTWISE-API-REFERENCE.md` — Read FIRST. Quick reference covering auth patterns, tiered endpoint map, key field mappings, and integration architecture flows.
|
||||||
|
2. `docs/connectwise/connectwise-psa-resolutionflow-reference.json` — Extracted OpenAPI 3.0.1 spec (v2025.16) with only the 670 endpoints and 342 schemas relevant to ResolutionFlow. Use for exact field types, request/response shapes, and parameter details.
|
||||||
|
3. `docs/connectwise/connectwise-psa-openapi-full.json` — Complete ConnectWise PSA OpenAPI spec (1838 endpoints, 842 schemas). Only consult if you need an endpoint outside the extracted subset.
|
||||||
|
|
||||||
|
### Integration Architecture
|
||||||
|
|
||||||
|
- **Session → Ticket Notes:** Post auto-generated session documentation to ConnectWise tickets as internal analysis notes via `POST /service/tickets/{id}/notes`
|
||||||
|
- **Ticket Context → Session Runner:** Pull ticket details, company info, and attached configurations to give FlowPilot AI real-world context
|
||||||
|
- **Callbacks:** Register webhooks via `/system/callbacks` for real-time ticket event notifications to suggest relevant Flows
|
||||||
|
|
||||||
|
### Key Implementation Rules
|
||||||
|
|
||||||
|
- Auth: API Key auth (Base64 of `companyId+publicKey:privateKey`) + `clientId` header on every request
|
||||||
|
- All ConnectWise integration code belongs in a dedicated service layer (e.g., `services/connectwise/`) — do NOT scatter CW API calls across the codebase
|
||||||
|
- Each MSP tenant provides their own CW credentials — ResolutionFlow stores these per-team, never per-user
|
||||||
|
- Design for the Autotask integration following the same service layer pattern (future PSA)
|
||||||
|
- Respect CW API: cache board/status/priority lookups, paginate with max 1000 per page, handle retries gracefully
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Development Commands
|
## Development Commands
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
@@ -190,11 +237,13 @@ gh run view <id> --json jobs --jq '.jobs[] | {name: .name, conclusion: .conclusi
|
|||||||
```
|
```
|
||||||
|
|
||||||
### URLs
|
### URLs
|
||||||
- Frontend: http://localhost:5173
|
|
||||||
- Backend API: http://localhost:8000
|
- Frontend: <http://localhost:5173>
|
||||||
- API Docs: http://localhost:8000/api/docs
|
- Backend API: <http://localhost:8000>
|
||||||
|
- API Docs: <http://localhost:8000/api/docs>
|
||||||
|
|
||||||
### Test Users (seeded via `scripts/seed_test_users.py`)
|
### Test Users (seeded via `scripts/seed_test_users.py`)
|
||||||
|
|
||||||
- All share password: `TestPass123!`
|
- All share password: `TestPass123!`
|
||||||
- `admin@resolutionflow.example.com` (super_admin), `teamadmin@resolutionflow.example.com` (team_admin), `engineer@resolutionflow.example.com` (engineer), `pro@resolutionflow.example.com` (solo pro)
|
- `admin@resolutionflow.example.com` (super_admin), `teamadmin@resolutionflow.example.com` (team_admin), `engineer@resolutionflow.example.com` (engineer), `pro@resolutionflow.example.com` (solo pro)
|
||||||
|
|
||||||
@@ -205,6 +254,7 @@ gh run view <id> --json jobs --jq '.jobs[] | {name: .name, conclusion: .conclusi
|
|||||||
### Top Gotchas (most commonly hit)
|
### Top Gotchas (most commonly hit)
|
||||||
|
|
||||||
**1. DateTime Handling — Always timezone-aware:**
|
**1. DateTime Handling — Always timezone-aware:**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# CORRECT
|
# CORRECT
|
||||||
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
|
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
|
||||||
@@ -212,6 +262,7 @@ created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezo
|
|||||||
```
|
```
|
||||||
|
|
||||||
**2. SQLAlchemy Async — No lazy loading on new objects:**
|
**2. SQLAlchemy Async — No lazy loading on new objects:**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# WRONG — MissingGreenlet error
|
# WRONG — MissingGreenlet error
|
||||||
new_tree = Tree(...); db.add(new_tree); await db.flush()
|
new_tree = Tree(...); db.add(new_tree); await db.flush()
|
||||||
@@ -222,6 +273,7 @@ await db.execute(tree_tag_assignments.insert().values(tree_id=new_tree.id, tag_i
|
|||||||
```
|
```
|
||||||
|
|
||||||
**3. React State — Don't store object snapshots:**
|
**3. React State — Don't store object snapshots:**
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
// WRONG — snapshot won't update
|
// WRONG — snapshot won't update
|
||||||
const [editingNode, setEditingNode] = useState(node)
|
const [editingNode, setEditingNode] = useState(node)
|
||||||
@@ -231,17 +283,20 @@ const editingNode = editingNodeId ? findNode(editingNodeId, tree?.tree_structure
|
|||||||
```
|
```
|
||||||
|
|
||||||
**4. Modal Draft State — Exclude store-managed fields:**
|
**4. Modal Draft State — Exclude store-managed fields:**
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
const { children, ...draftWithoutChildren } = draft
|
const { children, ...draftWithoutChildren } = draft
|
||||||
updateNode(node.id, draftWithoutChildren) // Don't overwrite children
|
updateNode(node.id, draftWithoutChildren) // Don't overwrite children
|
||||||
```
|
```
|
||||||
|
|
||||||
**5. Multiple FKs to same table — Specify `foreign_keys` on BOTH sides:**
|
**5. Multiple FKs to same table — Specify `foreign_keys` on BOTH sides:**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
author = relationship("User", foreign_keys=[author_id], back_populates="trees")
|
author = relationship("User", foreign_keys=[author_id], back_populates="trees")
|
||||||
```
|
```
|
||||||
|
|
||||||
**6. PostgreSQL NULL in UUID columns:**
|
**6. PostgreSQL NULL in UUID columns:**
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT 'tag', 'slug', NULL::uuid as team_id -- Must cast NULL to uuid
|
SELECT 'tag', 'slug', NULL::uuid as team_id -- Must cast NULL to uuid
|
||||||
```
|
```
|
||||||
@@ -255,6 +310,7 @@ SELECT 'tag', 'slug', NULL::uuid as team_id -- Must cast NULL to uuid
|
|||||||
**9. Public endpoints with optional auth:** Use manual `_get_optional_user(request, db)` helper, NOT `Optional[User]` param (FastAPI treats it as Pydantic field).
|
**9. Public endpoints with optional auth:** Use manual `_get_optional_user(request, db)` helper, NOT `Optional[User]` param (FastAPI treats it as Pydantic field).
|
||||||
|
|
||||||
**10. React Router — Clear dirty state before navigation:**
|
**10. React Router — Clear dirty state before navigation:**
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
markSaved() // Clear isDirty BEFORE navigate()
|
markSaved() // Clear isDirty BEFORE navigate()
|
||||||
navigate(`/trees/${newTree.id}/edit`)
|
navigate(`/trees/${newTree.id}/edit`)
|
||||||
@@ -408,19 +464,24 @@ navigate(`/trees/${newTree.id}/edit`)
|
|||||||
## Coding Standards
|
## Coding Standards
|
||||||
|
|
||||||
### Python
|
### Python
|
||||||
|
|
||||||
- Type hints everywhere, async/await for DB, Pydantic for validation, `DateTime(timezone=True)` always
|
- Type hints everywhere, async/await for DB, Pydantic for validation, `DateTime(timezone=True)` always
|
||||||
|
|
||||||
### TypeScript
|
### TypeScript
|
||||||
|
|
||||||
- Interfaces for all data, `const` over `let`, functional components + hooks, reusable logic in custom hooks
|
- Interfaces for all data, `const` over `let`, functional components + hooks, reusable logic in custom hooks
|
||||||
|
|
||||||
### Git
|
### Git
|
||||||
|
|
||||||
- Format: `type: description` (feat, fix, refactor, docs, test, chore)
|
- Format: `type: description` (feat, fix, refactor, docs, test, chore)
|
||||||
- Always include `Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>`
|
- Always include `Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>`
|
||||||
- Always create feature branch BEFORE committing: `git checkout -b feat/feature-name`
|
- Always create feature branch BEFORE committing: `git checkout -b feat/feature-name`
|
||||||
- Large features: commit per phase with `npm run build` validation
|
- Large features: commit per phase with `npm run build` validation
|
||||||
|
|
||||||
### After Completing Work
|
### After Completing Work
|
||||||
|
|
||||||
When a feature, fix, or significant piece of work is finished and merged/committed:
|
When a feature, fix, or significant piece of work is finished and merged/committed:
|
||||||
|
|
||||||
1. **Update `CURRENT-STATE.md`** — move completed items, update "In Progress" and "What's Next" sections
|
1. **Update `CURRENT-STATE.md`** — move completed items, update "In Progress" and "What's Next" sections
|
||||||
2. **Update `03-DEVELOPMENT-ROADMAP.md`** — check off completed work, update phase status
|
2. **Update `03-DEVELOPMENT-ROADMAP.md`** — check off completed work, update phase status
|
||||||
3. **Close related GitHub Issues** — use `gh issue close #N` for any issues resolved by the work
|
3. **Close related GitHub Issues** — use `gh issue close #N` for any issues resolved by the work
|
||||||
@@ -451,7 +512,7 @@ When a feature, fix, or significant piece of work is finished and merged/committ
|
|||||||
|
|
||||||
| What | Where |
|
| What | Where |
|
||||||
|------|-------|
|
|------|-------|
|
||||||
| API Docs | http://localhost:8000/api/docs |
|
| API Docs | <http://localhost:8000/api/docs> |
|
||||||
| Detailed Status | [CURRENT-STATE.md](CURRENT-STATE.md) |
|
| Detailed Status | [CURRENT-STATE.md](CURRENT-STATE.md) |
|
||||||
| Development Roadmap | [03-DEVELOPMENT-ROADMAP.md](03-DEVELOPMENT-ROADMAP.md) |
|
| Development Roadmap | [03-DEVELOPMENT-ROADMAP.md](03-DEVELOPMENT-ROADMAP.md) |
|
||||||
| GitHub Issues | `gh issue list --state open` |
|
| GitHub Issues | `gh issue list --state open` |
|
||||||
|
|||||||
Reference in New Issue
Block a user