# CLAUDE.md - Patherly / ResolutionFlow Project Context > **Purpose:** This file provides Claude Code with essential context for working on the Patherly project. > **Last Updated:** February 5, 2026 --- ## Project Overview **Patherly** (user-facing brand: **ResolutionFlow**) is a **SaaS product for Managed Service Provider (MSP) professionals and companies**. It provides troubleshooting decision trees that guide engineers through proven troubleshooting paths, capture decisions and notes automatically, and generate professional ticket documentation. **Tagline:** "Take the path MOST traveled." **Target Market:** MSP (Managed Service Provider) companies — IT service providers that manage infrastructure, endpoints, and support for multiple client organizations. **Primary User:** Michael Chihlas - Senior Systems Engineer at an MSP **Goal:** Michael uses this tool for 50% of his tickets within 3 months. **SaaS Context:** All features should be designed with multi-tenant MSP use in mind — teams represent MSP companies, trees are shared within teams, and the permission model supports tiered access (super_admin for platform, team_admin for MSP company, engineer for technicians, viewer for read-only access). ### Branding The project was rebranded from "Patherly" to "ResolutionFlow" in the frontend (PR #26, commit `cfbd815`). The naming is split: | Context | Name Used | |---------|-----------| | Repository / directory name | `patherly` | | Backend (FastAPI, env vars, APP_NAME) | ResolutionFlow | | Database / Docker container | `patherly` / `patherly_postgres` | | Production URLs | `resolutionflow.com` / `api.resolutionflow.com` | | **Frontend UI (header, login, register)** | **ResolutionFlow** | | **Browser tab title** | **ResolutionFlow - Decision Tree Platform** | **Brand details:** - **Colors:** Purple gradient (`#818cf8` → `#a78bfa`) - **Fonts:** Plus Jakarta Sans (headings), Inter (body), Outfit (labels) - loaded via Google Fonts - **Logo:** Inline SVG in `AppLayout.tsx` and `LoginPage.tsx` (decision-tree icon with gradient) - **Brand assets:** `brand-assets/` (source SVGs), `frontend/src/assets/brand/` (app assets), `frontend/public/icons/` (favicon) - **CSS utilities:** `text-gradient-brand`, `bg-gradient-brand`, `bg-gradient-brand-hover` (defined in `tailwind.config.js` and `index.css`) - **Rebrand guide:** [REBRAND-IMPLEMENTATION-GUIDE.md](REBRAND-IMPLEMENTATION-GUIDE.md) When adding new frontend pages or components, use "ResolutionFlow" for any user-visible branding. Use `font-heading` for heading elements and the `text-gradient-brand` utility for brand-colored text. --- ## Current State - **Phase:** Phase 2.5 - Step Library Foundation (In Progress) - **Backend:** Complete (25+ API endpoints, 60+ integration tests, all passing) - **Frontend:** Core features complete, Tree Editor functional, Settings page added - **Database:** PostgreSQL with Docker (container name: `patherly_postgres`) ### What's Complete - User authentication (JWT, register, login, refresh, invite codes) - Trees CRUD with full-text search - Sessions tracking with decisions - Export API (Markdown, Text, HTML) - Tree Editor with form-based editing and visual preview - Dark/Light theme toggle - Markdown rendering in session player and node editor - 7 comprehensive seed decision trees - **Tree Organization System:** - Categories (global + team-specific, admin-managed) - Tags (author + admin managed, autocomplete) - User folders (personal tree collections) - Subfolder hierarchy (max 3 levels deep) - Right-click context menu for edit/delete/add subfolder - Cascade delete for subfolders - Team admin role with scoped permissions - Filter trees by category, tags, and folders - **User Preferences (Issue #3):** - Settings page at `/settings` - Default export format preference (persisted in localStorage) - Theme toggle integrated in Settings - **Step Categories (Issue #5):** - Database table with 10 seeded global categories - Full CRUD API at `/api/v1/step-categories` - Team scoping support (global + team-specific) - **Step Library Schema (Issue #6):** - `step_library` table for reusable troubleshooting steps - `step_ratings` table for user ratings/reviews - `step_usage_log` table for tracking verified use - Support for decision/action/solution step types - Visibility levels: private, team, public - **Step Library API (Issue #7):** - Full CRUD at `/api/v1/steps` - Full-text search endpoint - Popular tags endpoint - Rating/review system with verified use tracking - **Frontend Rebrand (PR #26):** - Renamed from "Patherly" to "ResolutionFlow" in all user-facing UI - Purple gradient theme, custom fonts (Plus Jakarta Sans, Inter, Outfit) - Custom SVG logo in header and auth pages - Updated favicon and browser tab title - **Token Refresh Fix:** - Silent refresh with single-flight queue (prevents concurrent 401 race conditions) - Backend `get_refresh_token_payload` dependency extracts refresh token from Authorization header - Frontend Axios interceptor queues failed requests during refresh, retries after success - Auth store synced after silent refresh via `setTokens` action - **RBAC & Permissions:** - `is_super_admin` boolean on User model (migration 010) - Role hierarchy: super_admin > team_admin > engineer > viewer - `role` field values: 'engineer' | 'viewer' (no more 'admin') - Team Admin = `role='engineer'` + `is_team_admin=True` + valid `team_id` - Backend centralized: `backend/app/core/permissions.py` - Frontend hook: `frontend/src/hooks/usePermissions.ts` - Viewers CAN: browse trees, start sessions, rate steps - Viewers CANNOT: create/edit trees, steps, tags, categories - **Security Hardening (Phase A):** - Registration role field removed from `UserCreate` — hardcoded to `engineer` - HTML export XSS fix — all user content escaped via `html.escape()` - Secret key validator — rejects default key when `DEBUG=False` - Role CHECK constraint on `users` table (migration 011) - `test_admin` fixture fixed to properly grant `is_super_admin=True` - **Security Hardening (Phase B):** - B1: Tree access check on `start_session` via `can_access_tree` from permissions.py - B2: All inline permission helpers replaced with centralized imports from `permissions.py` - B3: `require_engineer_or_admin` checks `is_team_admin` before role check - B4: `is_active` field on User model (migration 012), enforced in `get_current_active_user` - B5: Admin user management endpoints at `/api/v1/admin/users/*` (6 endpoints) - B6: Rate limiting on auth endpoints via slowapi (disabled when `DEBUG=True`) - B7: Refresh token rotation — JTI-based revocation, meaningful logout (migration 013) - Access token TTL reduced from 15 to 5 minutes - All endpoints now use `get_current_active_user` (not `get_current_user`) - **Permissions UX (Phase C):** - Super admin bypass in tree list filter (`build_tree_access_filter` returns `sa_true()`) - Audit log table (`audit_logs`) with JSONB details, integrated at admin + tree delete endpoints - Soft delete for trees (`deleted_at` + `deleted_by` columns, migration 015) - ProtectedRoute supports optional `requiredRole` prop for role-based route guards - TreeEditorPage checks `canEditTree()` after fetch, before loading into editor - Reusable `ConfirmDialog` component + tree delete UI on library page - CustomStepModal hides "Type My Own" tab for users without `canCreateSteps` - **Permissions Cleanup (Phase D):** - Password complexity validation: requires uppercase, lowercase, and digit (min 10 chars) - Soft delete cascade: cleans up folder/tag junction entries on tree delete - Debug endpoint `/debug/cors` gated behind `if settings.DEBUG:` - Tag search escapes SQL wildcards (`%`, `_`) before LIKE query - **Session Scratchpad (Floating Overlay):** - Fixed-position overlay panel (420px wide, 55vh tall) on right edge - Floating button when collapsed, slide-in panel when expanded - Ctrl+/ keyboard shortcut to toggle - Auto-save with 1s debounce, markdown preview, localStorage persistence - Main content adjusts width via padding transition when panel opens - **Global Thin Scrollbar Styling:** - 6px thin scrollbars site-wide (Firefox `scrollbar-width: thin` + WebKit pseudo-elements) - Theme-aware colors using CSS variables (`--border`, `--muted-foreground`) ### What's In Progress - Custom step continuation flow refinements (Phase 2.5) - Tree forking from sessions with custom steps ### Deployment - **Production:** Railway (app.patherly.com / api.patherly.com) - **PR Environments:** Enabled - auto-created for each pull request --- ## Tech Stack ### Backend - **Framework:** Python FastAPI - **Database:** PostgreSQL 16 (async via SQLAlchemy 2.0 + asyncpg) - **Migrations:** Alembic - **Auth:** JWT tokens (python-jose) + bcrypt passwords - **Validation:** Pydantic v2 ### Frontend - **Framework:** React 19 + Vite + TypeScript - **Styling:** Tailwind CSS v3 with ResolutionFlow brand theme (purple gradient) - **Fonts:** Plus Jakarta Sans (headings), Inter (body), Outfit (labels) via Google Fonts - **State:** Zustand (with immer + zundo for undo/redo) - **Routing:** React Router v7 - **API Client:** Axios with token interceptors - **Icons:** Lucide React --- ## Project Structure ``` patherly/ ├── backend/ │ ├── app/ │ │ ├── main.py # FastAPI entry point │ │ ├── api/ │ │ │ ├── endpoints/ │ │ │ │ ├── auth.py # Auth: register, login, refresh, logout │ │ │ │ ├── admin.py # Admin user management (6 endpoints) │ │ │ │ ├── trees.py # Trees CRUD + search │ │ │ │ ├── sessions.py # Sessions + export │ │ │ │ ├── invite.py # Invite code management │ │ │ │ ├── categories.py # Tree categories (global + team) │ │ │ │ ├── tags.py # Tree tags + autocomplete │ │ │ │ ├── folders.py # User folders (hierarchy) │ │ │ │ ├── steps.py # Step library CRUD + search │ │ │ │ └── step_categories.py # Step categories │ │ │ ├── deps.py # Auth dependencies │ │ │ └── router.py │ │ ├── core/ │ │ │ ├── config.py # Settings (pydantic-settings) │ │ │ ├── database.py # Async SQLAlchemy │ │ │ ├── audit.py # Centralized audit log helper (log_audit) │ │ │ ├── permissions.py # Centralized RBAC (role checks, content guards) │ │ │ ├── rate_limit.py # Shared slowapi limiter (disabled in DEBUG) │ │ │ ├── security.py # JWT + password hashing + token rotation │ │ │ ├── logging_config.py # Structured logging │ │ │ └── middleware.py # Request logging │ │ ├── models/ # SQLAlchemy models │ │ │ ├── user.py # is_team_admin field added │ │ │ ├── team.py │ │ │ ├── tree.py # JSONB tree_structure + category_id, tags │ │ │ ├── session.py # JSONB path_taken, decisions │ │ │ ├── attachment.py │ │ │ ├── invite_code.py │ │ │ ├── category.py # TreeCategory model │ │ │ ├── tag.py # TreeTag model │ │ │ ├── folder.py # UserFolder model │ │ │ ├── step_category.py # StepCategory model │ │ │ ├── step_library.py # StepLibrary, StepRating, StepUsageLog │ │ │ ├── refresh_token.py # RefreshToken model (JTI-based revocation) │ │ │ └── audit_log.py # AuditLog model (JSONB details, indexed) │ │ └── schemas/ # Pydantic schemas │ ├── alembic/ # Database migrations │ ├── scripts/ │ │ ├── seed_data.py │ │ └── seed_trees.py # 7 comprehensive trees │ ├── tests/ # pytest integration tests │ ├── docker-compose.yml # PostgreSQL container │ ├── requirements.txt │ └── .env # Environment variables │ ├── brand-assets/ # Source brand SVGs and guides │ ├── frontend/ │ ├── public/ │ │ └── icons/ # Favicon and PWA icons │ ├── src/ │ │ ├── main.tsx │ │ ├── App.tsx │ │ ├── router.tsx │ │ ├── assets/brand/ # Brand logos (SVG) │ │ ├── api/ # Axios API client │ │ │ ├── client.ts # Axios instance with refresh queue interceptor │ │ │ ├── auth.ts │ │ │ ├── trees.ts │ │ │ └── sessions.ts │ │ ├── hooks/ # Custom React hooks (useKeyboardShortcuts, usePermissions) │ │ ├── store/ │ │ │ ├── authStore.ts # Zustand auth state │ │ │ ├── themeStore.ts # Dark/light theme │ │ │ ├── treeEditorStore.ts # Tree editor state (immer + zundo) │ │ │ └── userPreferencesStore.ts # User preferences (NEW) │ │ ├── components/ │ │ │ ├── common/ # Modal, ErrorBoundary, ThemeToggle, ConfirmDialog │ │ │ ├── layout/ # AppLayout, ProtectedRoute │ │ │ ├── tree-editor/ # Tree editor components │ │ │ ├── tree-preview/ # Visual tree preview │ │ │ ├── step-library/ # Step library browser, forms, modals │ │ │ ├── library/ # Tree library UI components │ │ │ ├── session/ # Session modals, scratchpad floating overlay │ │ │ └── ui/ # MarkdownContent │ │ ├── pages/ │ │ │ ├── LoginPage.tsx │ │ │ ├── RegisterPage.tsx │ │ │ ├── TreeLibraryPage.tsx │ │ │ ├── TreeNavigationPage.tsx # Core feature │ │ │ ├── TreeEditorPage.tsx │ │ │ ├── SessionHistoryPage.tsx │ │ │ ├── SessionDetailPage.tsx │ │ │ └── SettingsPage.tsx # User preferences (NEW) │ │ ├── types/ # TypeScript interfaces │ │ └── lib/utils.ts # cn() utility for Tailwind │ ├── package.json │ ├── tailwind.config.js │ └── vite.config.ts │ ├── CLAUDE.md # This file ├── CURRENT-STATE.md # Quick status reference ├── LESSONS-LEARNED.md # Bugs and fixes (READ THIS!) ├── PROGRESS.md # Detailed progress log ├── REBRAND-IMPLEMENTATION-GUIDE.md # Patherly → ResolutionFlow rebrand guide ├── docs/plans/ # Design docs & implementation plans (YYYY-MM-DD--design.md / -implementation.md) ├── 01-PROJECT-OVERVIEW.md # Vision and goals ├── 02-TECHNICAL-ARCHITECTURE.md # System design, API specs ├── 03-DEVELOPMENT-ROADMAP.md # Phases and timeline ├── 04-FEATURE-SPECIFICATIONS.md # Feature details ├── 05-QUESTIONS-AND-ACTION-ITEMS.md └── PHASE-2.5-PERSONAL-BRANCHING.md # Future feature spec ``` --- ## Environment Variables ### Backend (.env) **Required in `backend/.env`:** ```bash # Application APP_NAME=ResolutionFlow DEBUG=true # Set false in production # Database (matches docker-compose.yml) DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/patherly DATABASE_URL_SYNC=postgresql://postgres:postgres@localhost:5432/patherly # JWT Settings - CHANGE THESE IN PRODUCTION SECRET_KEY= ACCESS_TOKEN_EXPIRE_MINUTES=5 # Reduced from 15; refresh tokens handle session continuity REFRESH_TOKEN_EXPIRE_DAYS=7 # Auth REQUIRE_INVITE_CODE=true # Set false to allow open registration ``` **Railway-specific (production):** ```bash ALLOW_RAILWAY_ORIGINS=true # Enables CORS for PR environments ``` ### Frontend (.env.local - optional) ```bash VITE_API_URL=http://localhost:8000 # Override API URL ``` --- ## Development Commands ### Start Development Environment ```powershell # Terminal 1: Start PostgreSQL docker start patherly_postgres # Terminal 2: Backend (from project root) cd backend # Windows: .\venv\Scripts\Activate # Linux/Mac: source venv/bin/activate uvicorn app.main:app --reload # Terminal 3: Frontend (from project root) cd frontend npm run dev ``` ### URLs - Frontend: http://localhost:5173 - Backend API: http://localhost:8000 - API Docs: http://localhost:8000/api/docs ### Run Tests ```powershell # From project root cd backend # First time only: create test database docker exec -it patherly_postgres psql -U postgres -c "CREATE DATABASE patherly_test;" # Install test dependencies (if not already installed) pip install -r requirements-dev.txt # Run tests pytest --override-ini="addopts=" ``` ### Frontend Operations ```powershell # From project root cd frontend # Build for production npm run build # Preview production build npm run preview # Lint code npm run lint ``` ### Run Seed Scripts ```powershell # From project root cd backend pip install httpx # Required for seed scripts python -m scripts.seed_trees ``` ### Database Operations ```powershell # From project root cd backend # Run migrations alembic upgrade head # Create new migration alembic revision --autogenerate -m "Description" # Create migration without DB running (manual mode) alembic revision -m "Description" # Edit migration file manually # Migration runs when DB is available - safe to commit without testing locally # Find current migration head (check down_revision in latest file) # Chain mixes numeric IDs (001-008) and hex hashes (e.g., 4cdb5cba1aff) # Always inspect the actual files — don't assume sequential ordering # Access PostgreSQL (no local psql needed) docker exec -it patherly_postgres psql -U postgres -d patherly ``` --- ## Critical Lessons Learned **ALWAYS read [LESSONS-LEARNED.md](LESSONS-LEARNED.md) before making changes!** ### DateTime Handling (Critical) ```python # CORRECT - Always use timezone-aware datetimes from datetime import datetime, timezone from sqlalchemy import DateTime created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) # WRONG - Never use this datetime.utcnow() # Deprecated, returns naive datetime ``` ### React State: Don't Store Object Snapshots ```tsx // WRONG - Snapshot won't update when store changes const [editingNode, setEditingNode] = useState(null) // CORRECT - Store ID only, fetch current object each render const [editingNodeId, setEditingNodeId] = useState(null) const editingNode = editingNodeId ? findNode(editingNodeId) : null ``` ### Modal Draft State: Don't Overwrite Store-Managed Fields ```tsx // WRONG - Overwrites children with stale snapshot const handleSave = () => { updateNode(node.id, draft) // draft.children is stale! } // CORRECT - Exclude store-managed fields const handleSave = () => { const { children, ...draftWithoutChildren } = draft updateNode(node.id, draftWithoutChildren) } ``` ### Database Name - Database name is `patherly` (not `decision_tree`) - Update `.env` if you see the old name ### Virtual Environment - Always check for `(venv)` prefix before running pip - Don't use `--break-system-packages` when venv is active ### PostgreSQL NULL Casting for UUID Columns ```sql -- WRONG - PostgreSQL infers NULL as text type INSERT INTO tree_tags (name, slug, team_id) SELECT 'tag', 'slug', NULL as team_id -- Error: column is uuid but expression is text -- CORRECT - Explicitly cast NULL to uuid INSERT INTO tree_tags (name, slug, team_id) SELECT 'tag', 'slug', NULL::uuid as team_id -- Works! ``` Always use `NULL::uuid` when inserting NULL values into UUID columns in raw SQL. ### SQLAlchemy Async: Avoid Lazy Loading on New Objects ```python # WRONG - Triggers lazy load which fails in async context new_tree = Tree(...) db.add(new_tree) await db.flush() new_tree.tags.append(tag) # MissingGreenlet error! # CORRECT - Use direct SQL for junction tables from app.models.tag import tree_tag_assignments await db.execute( tree_tag_assignments.insert().values( tree_id=new_tree.id, tag_id=tag.id ) ) ``` Accessing relationships on newly created objects triggers lazy loading, which fails in async SQLAlchemy. Use direct SQL inserts for junction tables instead. ### SQLAlchemy: Multiple FKs to Same Table Require `foreign_keys` When a model has two FKs to the same table (e.g., Tree has `author_id` and `deleted_by` both pointing to `users`), specify `foreign_keys` on BOTH sides: ```python # On Tree model author = relationship("User", foreign_keys=[author_id], back_populates="trees") # On User model trees = relationship("Tree", foreign_keys="[Tree.author_id]", back_populates="author") ``` ### React Router: Clear Dirty State Before Navigation ```tsx // WRONG - Navigation triggers before dirty flag is cleared const newTree = await treesApi.create(data) navigate(`/trees/${newTree.id}/edit`) // Blocker fires here! markSaved() // Too late // CORRECT - Clear dirty state first const newTree = await treesApi.create(data) markSaved() // Clear isDirty first navigate(`/trees/${newTree.id}/edit`) // Blocker won't fire ``` When using `useBlocker` for unsaved changes, always clear the dirty flag before programmatic navigation. ### CORS: Include Both allow_origins AND allow_origin_regex ```python # WRONG - Custom domains ignored when using regex if settings.ALLOW_RAILWAY_ORIGINS: app.add_middleware( CORSMiddleware, allow_origin_regex=r"https://.*\.up\.railway\.app", # Only matches Railway domains! # ... ) # CORRECT - Include both for custom domains + Railway PR environments if settings.ALLOW_RAILWAY_ORIGINS: app.add_middleware( CORSMiddleware, allow_origins=settings.allowed_origins, # Custom domains like resolutionflow.com allow_origin_regex=r"https://.*\.up\.railway\.app", # Railway PR domains # ... ) ``` When using `allow_origin_regex` for wildcard patterns, also include `allow_origins` for explicit custom domains. The regex alone won't match custom domains like `resolutionflow.com`. ### RBAC Permission Checks - Backend auth deps: `get_current_active_user` (any logged-in + active), `require_engineer_or_admin` (blocks viewers), `require_admin` (super admin only) - Backend: `is_super_admin` replaces all `role == "admin"` checks. Never use `role == "admin"`. - Frontend: use `usePermissions()` hook for all role/permission checks - `TreeListItem` includes `team_id` for frontend permission checks (`author_id` and `team_id` are nullable) ### SQLAlchemy Test Fixtures: Circular FK Handling The `users ↔ invite_codes` tables have circular foreign keys. `Base.metadata.drop_all()` fails with `CircularDependencyError`. The test fixture uses `DROP SCHEMA public CASCADE` instead (split into two `sa.text()` calls — asyncpg rejects multi-statement strings). ### Alembic Migrations: Test Data State Before Writing WHERE Clauses Migration 010 had `WHERE role = 'admin'` but the only user already had `role = 'engineer'` (changed by earlier work), so the UPDATE matched zero rows. Always verify actual data values before writing conditional migrations, or use broader conditions. ### findNode Requires Tree Structure Parameter ```tsx // WRONG - Always returns null (structure defaults to undefined) const node = findNode(nodeId) // CORRECT - Pass tree structure explicitly const node = findNode(nodeId, tree?.tree_structure) // For custom steps, also check: const customStep = findCustomStep(nodeId) ``` ### Frontend/Backend API Path Alignment Always verify frontend API paths match backend route definitions. Example: backend serves `/steps/tags/popular` but frontend called `/steps/popular-tags`. Check `backend/app/api/router.py` and endpoint files for actual paths. ### Custom Step Flow in Tree Navigation The custom step insertion flow in `TreeNavigationPage.tsx` chains through multiple modals: 1. `CustomStepModal` (create/browse) → 2. `PostStepActionModal` (save/use/both) → 3. `ContinuationModal` (pick descendant or build branch) → 4. Custom step view with "Continue to" button Key state: `pendingStep`, `pendingContinuationNodeId`, `customBranchMode`, `branchOriginNodeId` Custom steps are stored in session JSONB (`custom_steps` field) and referenced by UUID in `pathTaken`. `findNode()` only searches tree structure -- use `findCustomStep()` for custom step UUIDs. ### Token Refresh: Match Frontend/Backend Contract The refresh endpoint must accept tokens the same way the frontend sends them. ```python # WRONG - Expects bare string, but frontend sends Authorization header @router.post("/refresh") async def refresh_token(refresh_token: str): payload = decode_token(refresh_token) # CORRECT - Use dependency that reads from Authorization header @router.post("/refresh") async def refresh_token( payload: Annotated[dict, Depends(get_refresh_token_payload)], ): ``` The frontend Axios interceptor sends `Authorization: Bearer `. The backend must extract it from the header, not expect it as a query/body parameter. ### CORS Errors Can Mask Server 500s When the backend returns a 500 Internal Server Error, CORS headers are not added to the response. The browser reports this as a CORS error, hiding the real cause. Always check backend logs first when debugging CORS issues locally. ### Run Migrations Before Local Testing After cloning or pulling new changes, always run `alembic upgrade head` before starting the backend. Missing migrations cause 500 errors (e.g., `column does not exist`) that manifest as CORS errors in the browser. --- ## API Endpoints Reference **Full API documentation:** http://localhost:8000/api/docs (interactive OpenAPI/Swagger UI) **Quick reference:** - **Auth:** `/api/v1/auth/*` - register, login, refresh, logout, me - **Trees:** `/api/v1/trees/*` - CRUD, search, categories (supports filters: category_id, tags, folder_id) - **Sessions:** `/api/v1/sessions/*` - start, track, complete, export (markdown/text/html) - **Tags:** `/api/v1/tags/*` - manage tree tags, autocomplete search - **Folders:** `/api/v1/folders/*` - user folders with subfolder hierarchy (max 3 levels deep) - **Categories:** `/api/v1/categories/*` - tree categories (global + team-specific) - **Step Categories:** `/api/v1/step-categories/*` - step categories for library - **Step Library:** `/api/v1/steps/*` - reusable steps, ratings, reviews, full-text search - **Admin:** `/api/v1/admin/users/*` - user management (list, get, change role, toggle team admin, deactivate, activate) - **Invite Codes:** `/api/v1/invite-codes/*` - admin management **Key constraints:** - Folder hierarchy: max 3 levels deep (root → child → grandchild) - Step ratings: 1-5 stars with optional review text - Categories and tags: support both global and team-specific scoping For detailed parameters, request/response schemas, and examples, visit the API docs. --- ## Data Models Key JSONB structures stored in PostgreSQL. See `frontend/src/types/` for full TypeScript interfaces. - **Tree Structure:** Recursive node tree (`decision`/`action`/`solution` types with `children[]`). See `types/tree.ts`. - **Session Decisions:** Array of `{node_id, question, answer, notes, timestamp}`. Timestamps are ISO strings, not datetime objects. --- ## Frontend Patterns ### State Management - **Auth:** `useAuthStore` - Zustand with localStorage persistence (includes `setTokens` for silent refresh sync) - **Theme:** `useThemeStore` - Dark/light/system preference - **Tree Editor:** `useTreeEditorStore` - Zustand + immer + zundo (undo/redo) - **User Preferences:** `useUserPreferencesStore` - Zustand with localStorage persistence (export format default) ### Component Guidelines - Use `cn()` from `@/lib/utils` for Tailwind class merging - Use Lucide icons (no `title` prop - wrap in `` instead) - Modals: Use fixed header/footer with scrollable body - Modals: Import and render at end of parent component JSX - Forms: Show field-level validation errors - Conditional rendering: Add null checks when node might not exist (`currentNode && currentNode.type`) ### TypeScript Type Organization - New type modules: Create in `types/` directory (e.g., `types/step.ts`) - Export from `types/index.ts` with `export * from './modulename'` - Import types using `import type { Type } from '@/types'` (type-only import) ### API Client Pattern ```typescript import api from '@/api/client' // Token refresh handled automatically by interceptor // Concurrent 401s are queued — only one refresh request fires at a time // On refresh failure, user is logged out and redirected to /login const response = await api.get('/api/v1/trees') ``` ### Floating Overlay Pattern (Scratchpad) The scratchpad uses `position: fixed` with an `onOpenChange` callback so the parent page can adjust layout: ```tsx // Child: ScratchpadSidebar.tsx onOpenChange?: (isOpen: boolean) => void // Fires when collapsed state changes, parent uses it to add/remove padding // Parent: TreeNavigationPage.tsx const [scratchpadOpen, setScratchpadOpen] = useState(...)
{/* centers in available space */} ``` Position overlay at `right-2` (not `right-0`) so it sits inside the page scrollbar, and use full `rounded-lg` (not `rounded-l-lg`). --- ## Common Tasks - **New endpoint:** Create in `endpoints/` → add to `router.py` → schema in `schemas/` → tests → frontend API client - **New page:** Create in `pages/` → add route in `router.tsx` → nav link in `AppLayout.tsx` - **Schema change:** Update model → `alembic revision --autogenerate -m "desc"` → 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 (Backend) - Use type hints everywhere - Use async/await for database operations - Use Pydantic for validation - Log with correlation IDs for tracing - Always use `DateTime(timezone=True)` for timestamps ### TypeScript (Frontend) - Enable strict mode (when ready) - Use TypeScript interfaces for all data structures - Prefer `const` over `let` - Use functional components with hooks - Extract reusable logic into custom hooks ### Git - Commit message format: `type: description` - Types: `feat`, `fix`, `refactor`, `docs`, `test`, `chore` - Always include `Co-Authored-By: Claude Opus 4.6 ` - Always create a feature branch BEFORE committing new work (not retroactively after committing to main) - PR workflow: `git checkout -b feat/feature-name` → commit → `git push -u origin feat/feature-name` → `gh pr create` ### Commit Strategy for Large Features - Break implementation into logical phases (foundation → components → integration) - Commit after each phase with `npm run build` validation - Phase commits enable easier debugging and rollback - Example: API clients (Phase 1) → UI components (Phase 2) → Page integration (Phase 3) --- ## Future Roadmap ### Phase 2.5 (In Progress) - Custom step continuation flow refinements - Tree forking from sessions with custom steps ### Phase 3 — File attachments, offline mode, client context, analytics ### Phase 4 — PSA integrations (ConnectWise, Kaseya), PowerShell automation, enterprise SSO --- ## Troubleshooting ### Backend won't start 1. Check Docker: `docker ps` - is `patherly_postgres` running? 2. Check `.env` - is DATABASE_URL correct (`patherly` not `decision_tree`)? 3. Check venv: is `(venv)` prefix showing? ### Frontend compile errors 1. Check `frontend/src/lib/utils.ts` exists (provides `cn()` function) 2. Run `npm install` to ensure dependencies are installed ### Tests failing 1. Create test database: `docker exec -it patherly_postgres psql -U postgres -c "CREATE DATABASE patherly_test;"` 2. Install dev deps: `pip install -r requirements-dev.txt` 3. Ensure pytest-asyncio version: `pip install pytest-asyncio==0.24.0` 4. If `unrecognized arguments: --cov` error: run with `--override-ini="addopts="` or install `pytest-cov` ### API 500 errors 1. Check server logs for datetime errors (timezone issue) 2. Ensure all datetimes use `datetime.now(timezone.utc)` --- ## Git Patterns **Always gitignore:** ``` # Secrets and local config backend/.env .claude.local.md .claude/settings.local.json # Dependencies backend/venv/ frontend/node_modules/ # Build artifacts frontend/dist/ backend/**/__pycache__/ *.pyc # Railway CLI (local tooling only) /node_modules/ /package.json /package-lock.json # IDE .vscode/ .idea/ *.swp ``` --- ## Quick Reference | What | Where | |------|-------| | API Docs | http://localhost:8000/api/docs | | Current Status | [CURRENT-STATE.md](CURRENT-STATE.md) | | Bug Fixes | [LESSONS-LEARNED.md](LESSONS-LEARNED.md) | | Feature Specs | [04-FEATURE-SPECIFICATIONS.md](04-FEATURE-SPECIFICATIONS.md) | | Phase 2.5 Spec | [PHASE-2.5-PERSONAL-BRANCHING.md](PHASE-2.5-PERSONAL-BRANCHING.md) | | Rebrand Guide | [REBRAND-IMPLEMENTATION-GUIDE.md](REBRAND-IMPLEMENTATION-GUIDE.md) | --- ## Railway Deployment ### Production - **Frontend:** - **Backend:** - **Database:** Railway-managed PostgreSQL - Deploys automatically on push to `main` ### PR Environments Railway creates isolated preview environments for each pull request. **Workflow:** 1. Create feature branch: `git checkout -b feature/my-feature` 2. Make changes, commit, push to origin 3. Open Pull Request on GitHub 4. Railway auto-creates preview environment (backend + frontend + DB) 5. **Generate domains manually** in Railway dashboard: - Switch to the PR environment - Click on each service → Settings → Networking → Generate Domain 6. **Set `VITE_API_URL`** on frontend service to point to the PR backend URL - **IMPORTANT:** Must include `https://` prefix (e.g., `https://patherly-patherly-pr-24.up.railway.app`) 7. Redeploy frontend if needed 8. Test at preview URLs 9. Merge PR → auto-deploys to production 10. Railway cleans up PR environment after merge **Environment Variables:** - PR environments inherit from `production` base environment - `REQUIRE_INVITE_CODE=true` is inherited (create invite codes in PR DB if needed) - `DATABASE_URL` is auto-provided for isolated PR database - `ALLOW_RAILWAY_ORIGINS=true` (shared variable) - enables CORS for all `*.up.railway.app` origins **Notes:** - Each PR gets a fresh database - no existing users/trees - Migrations run automatically via `releaseCommand` - Domains must be generated manually for each PR service **Debug Endpoints (only when `DEBUG=True`):** - `/debug/cors` - Check CORS configuration (requires `DEBUG=True` in environment) ### Railway CLI (Local) Railway CLI installed via npm in project root (gitignored). Services: `patherly` (backend), `hopeful-liberation` (frontend), `Postgres` (DB). ```powershell # Common commands (run from project root) ./node_modules/.bin/railway service status --all ./node_modules/.bin/railway service logs --service patherly ./node_modules/.bin/railway variables --service patherly ``` --- ## Contact **Primary User:** Michael Chihlas **Communication:** GitHub Issues / Direct chat