Files
resolutionflow/CLAUDE.md
chihlasm fafdaa50a5 Add tree organization system with categories, tags, and folders
Features:
- Categories: Global and team-specific tree categorization (admin-managed)
- Tags: Flexible tree tagging with autocomplete (author + admin)
- User folders: Personal tree collections with subfolder support
  - Hierarchical structure (max 3 levels deep)
  - Right-click context menu for folder management
  - Cascade delete for subfolders
- Filter trees by category, tags, and folder in library view

Backend:
- New models: Category, Tag, UserFolder with relationships
- New API endpoints for categories, tags, and folders
- Tree organization migrations (005, 006)

Frontend:
- FolderSidebar with hierarchical folder tree
- FolderEditModal for create/edit with color picker
- AddToFolderMenu for quick tree organization
- TagInput with autocomplete and TagBadges display
- Updated TreeMetadataForm and TreeLibraryPage

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 01:31:13 -05:00

18 KiB

CLAUDE.md - Patherly Project Context

Purpose: This file provides Claude Code with essential context for working on the Patherly project. Last Updated: February 2, 2026


Project Overview

Patherly is a troubleshooting decision tree application designed for MSP engineers. It guides engineers through proven troubleshooting paths, captures decisions and notes automatically, and generates professional ticket documentation.

Tagline: "Take the path MOST traveled."

Primary User: Michael Chihlas - Senior Systems Engineer at an MSP

Goal: Michael uses this tool for 50% of his tickets within 3 months.


Current State

  • Phase: Phase 2 - Tree Editor (In Progress)
  • Backend: Complete (18 API endpoints, 40+ integration tests, all passing)
  • Frontend: Core features complete, Tree Editor functional
  • 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

What's In Progress

  • User preferences (export format default)
  • Deployment to Railway/Render

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
  • 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
│   │   │   │   ├── trees.py        # Trees CRUD + search
│   │   │   │   ├── sessions.py     # Sessions + export
│   │   │   │   └── invite.py       # Invite code management
│   │   │   ├── deps.py             # Auth dependencies
│   │   │   └── router.py
│   │   ├── core/
│   │   │   ├── config.py           # Settings (pydantic-settings)
│   │   │   ├── database.py         # Async SQLAlchemy
│   │   │   ├── security.py         # JWT + password hashing
│   │   │   ├── 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 (NEW)
│   │   │   ├── tag.py              # TreeTag model (NEW)
│   │   │   └── folder.py           # UserFolder model (NEW)
│   │   └── 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
│
├── frontend/
│   ├── src/
│   │   ├── main.tsx
│   │   ├── App.tsx
│   │   ├── router.tsx
│   │   ├── api/                    # Axios API client
│   │   │   ├── client.ts           # Axios instance with interceptors
│   │   │   ├── auth.ts
│   │   │   ├── trees.ts
│   │   │   └── sessions.ts
│   │   ├── store/
│   │   │   ├── authStore.ts        # Zustand auth state
│   │   │   ├── themeStore.ts       # Dark/light theme
│   │   │   └── treeEditorStore.ts  # Tree editor state (immer + zundo)
│   │   ├── components/
│   │   │   ├── common/             # Modal, ErrorBoundary, ThemeToggle
│   │   │   ├── layout/             # AppLayout, ProtectedRoute
│   │   │   ├── tree-editor/        # Tree editor components
│   │   │   ├── tree-preview/       # Visual tree preview
│   │   │   └── ui/                 # MarkdownContent
│   │   ├── pages/
│   │   │   ├── LoginPage.tsx
│   │   │   ├── RegisterPage.tsx
│   │   │   ├── TreeLibraryPage.tsx
│   │   │   ├── TreeNavigationPage.tsx  # Core feature
│   │   │   ├── TreeEditorPage.tsx
│   │   │   ├── SessionHistoryPage.tsx
│   │   │   └── SessionDetailPage.tsx
│   │   ├── 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
├── 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

Development Commands

Start Development Environment

# Terminal 1: Start PostgreSQL
docker start patherly_postgres

# Terminal 2: Backend
cd C:\Dev\Projects\patherly\patherly\backend
.\venv\Scripts\Activate
uvicorn app.main:app --reload

# Terminal 3: Frontend
cd C:\Dev\Projects\patherly\patherly\frontend
npm run dev

URLs

Run Tests

cd C:\Dev\Projects\patherly\patherly\backend
.\venv\Scripts\Activate
pytest

Run Seed Scripts

cd C:\Dev\Projects\patherly\patherly\backend
.\venv\Scripts\Activate
pip install httpx  # Required for seed scripts
python -m scripts.seed_trees

Database Operations

# Run migrations
cd backend
alembic upgrade head

# Create new migration
alembic revision --autogenerate -m "Description"

# Access PostgreSQL (no local psql needed)
docker exec -it patherly_postgres psql -U postgres -d patherly

Critical Lessons Learned

ALWAYS read LESSONS-LEARNED.md before making changes!

DateTime Handling (Critical)

# 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

// WRONG - Snapshot won't update when store changes
const [editingNode, setEditingNode] = useState<TreeStructure | null>(null)

// CORRECT - Store ID only, fetch current object each render
const [editingNodeId, setEditingNodeId] = useState<string | null>(null)
const editingNode = editingNodeId ? findNode(editingNodeId) : null

Modal Draft State: Don't Overwrite Store-Managed Fields

// 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

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

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

React Router: Clear Dirty State Before Navigation

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


API Endpoints Reference

Authentication

POST /api/v1/auth/register     - Register (requires invite code by default)
POST /api/v1/auth/login        - Login (form data)
POST /api/v1/auth/login/json   - Login (JSON body)
POST /api/v1/auth/refresh      - Refresh access token
GET  /api/v1/auth/me           - Get current user
POST /api/v1/auth/logout       - Logout

Trees

GET    /api/v1/trees              - List trees (filters: category_id, tags, folder_id)
POST   /api/v1/trees              - Create tree (supports tags, category_id)
GET    /api/v1/trees/categories   - Get legacy string categories
GET    /api/v1/trees/search       - Full-text search
GET    /api/v1/trees/{id}         - Get tree (includes tags, category_info)
PUT    /api/v1/trees/{id}         - Update tree
DELETE /api/v1/trees/{id}         - Soft delete

Categories (NEW)

GET    /api/v1/categories         - List categories (global + user's team)
POST   /api/v1/categories         - Create category (team/global admin)
GET    /api/v1/categories/{id}    - Get category
PUT    /api/v1/categories/{id}    - Update category
DELETE /api/v1/categories/{id}    - Soft delete category

Tags (NEW)

GET    /api/v1/tags               - List tags (global + user's team)
GET    /api/v1/tags/search        - Autocomplete search
POST   /api/v1/tags               - Create tag
GET    /api/v1/tags/{id}          - Get tag
GET    /api/v1/tags/trees/{id}    - Get tree's tags
POST   /api/v1/tags/trees/{id}    - Add tags to tree
PUT    /api/v1/tags/trees/{id}    - Replace tree's tags
DELETE /api/v1/tags/trees/{id}/{slug} - Remove tag from tree

Folders (NEW)

GET    /api/v1/folders            - List user's folders (includes parent_id)
POST   /api/v1/folders            - Create folder (supports parent_id for subfolders)
GET    /api/v1/folders/{id}       - Get folder
PUT    /api/v1/folders/{id}       - Update folder (supports moving via parent_id)
DELETE /api/v1/folders/{id}       - Delete folder (cascades to subfolders)
POST   /api/v1/folders/reorder    - Reorder folders
POST   /api/v1/folders/{id}/trees - Add tree to folder
GET    /api/v1/folders/{id}/trees - Get folder's tree IDs
DELETE /api/v1/folders/{id}/trees/{tree_id} - Remove tree from folder

Folder hierarchy constraints:

  • Max nesting depth: 3 levels (root → child → grandchild)
  • Same folder name allowed under different parents
  • Moving folders validates cycle prevention

Sessions

GET  /api/v1/sessions             - List user's sessions
POST /api/v1/sessions             - Start session
GET  /api/v1/sessions/{id}        - Get session
PUT  /api/v1/sessions/{id}        - Update session
POST /api/v1/sessions/{id}/complete - Complete session
POST /api/v1/sessions/{id}/export   - Export (md/txt/html)

Invite Codes

GET  /api/v1/invite-codes         - List codes (admin)
POST /api/v1/invite-codes         - Create code (admin)
GET  /api/v1/invite-codes/validate/{code} - Validate code

Data Models

Tree Structure (JSONB)

interface TreeStructure {
  id: string
  type: 'decision' | 'action' | 'solution'

  // Decision nodes
  question?: string
  help_text?: string
  options?: Array<{
    id: string
    label: string
    next_node_id?: string
  }>

  // Action nodes
  title?: string
  description?: string
  commands?: string[]
  next_node_id?: string

  // Solution nodes
  title?: string
  description?: string
  steps?: string[]

  // All nodes can have children
  children?: TreeStructure[]
}

Session Decisions (JSONB)

interface Decision {
  node_id: string
  question?: string
  answer: string
  notes?: string
  timestamp: string  // ISO string, not datetime object
}

Frontend Patterns

State Management

  • Auth: useAuthStore - Zustand with localStorage persistence
  • Theme: useThemeStore - Dark/light/system preference
  • Tree Editor: useTreeEditorStore - Zustand + immer + zundo (undo/redo)

Component Guidelines

  • Use cn() from @/lib/utils for Tailwind class merging
  • Use Lucide icons (no title prop - wrap in <span> instead)
  • Modals: Use fixed header/footer with scrollable body
  • Forms: Show field-level validation errors

API Client Pattern

import api from '@/api/client'

// Token refresh handled automatically by interceptor
const response = await api.get('/api/v1/trees')

Common Tasks

Adding a New API Endpoint

  1. Create route in backend/app/api/endpoints/
  2. Add to router in backend/app/api/router.py
  3. Create/update Pydantic schemas in backend/app/schemas/
  4. Add tests in backend/tests/
  5. Update API client in frontend/src/api/

Adding a New Page

  1. Create page component in frontend/src/pages/
  2. Add route in frontend/src/router.tsx
  3. Add navigation link in AppLayout.tsx if needed

Modifying Database Schema

  1. Update model in backend/app/models/
  2. Create migration: alembic revision --autogenerate -m "description"
  3. Review generated migration file
  4. Apply: alembic upgrade head

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.5 <noreply@anthropic.com>

Future Roadmap

Phase 2.5 (Planned)

  • Personal tree branching (add custom steps during sessions)
  • Step library with categories, tags, and ratings
  • Tree forking and sharing

Phase 3 (Planned)

  • File attachments (screenshots, logs)
  • Offline mode (Service Workers + IndexedDB)
  • Client context system
  • Analytics dashboard

Phase 4 (Planned)

  • API & integrations (ConnectWise, Kaseya)
  • PowerShell automation execution
  • Enterprise features (SSO, white-labeling)

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

API 500 errors

  1. Check server logs for datetime errors (timezone issue)
  2. Ensure all datetimes use datetime.now(timezone.utc)

Quick Reference

What Where
API Docs http://localhost:8000/api/docs
Current Status CURRENT-STATE.md
Bug Fixes LESSONS-LEARNED.md
Feature Specs 04-FEATURE-SPECIFICATIONS.md
Phase 2.5 Spec PHASE-2.5-PERSONAL-BRANCHING.md

Contact

Primary User: Michael Chihlas Communication: GitHub Issues / Direct chat