Files
resolutionflow/CLAUDE.md
2026-02-04 21:46:32 -05:00

32 KiB

CLAUDE.md - Patherly / ResolutionFlow Project Context

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


Project Overview

Patherly (user-facing brand: ResolutionFlow) 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.

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

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 (20+ API endpoints, 40+ 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
  • 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
│   │   │   │   ├── 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
│   │   │   ├── tag.py              # TreeTag model
│   │   │   ├── folder.py           # UserFolder model
│   │   │   ├── step_category.py    # StepCategory model (NEW)
│   │   │   └── step_library.py     # StepLibrary, StepRating, StepUsageLog (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
│
├── 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)
│   │   ├── 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
│   │   │   ├── layout/             # AppLayout, ProtectedRoute
│   │   │   ├── tree-editor/        # Tree editor components
│   │   │   ├── tree-preview/       # Visual tree preview
│   │   │   ├── step-library/       # Step library browser, forms, modals
│   │   │   ├── 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-<topic>-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:

# 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=<generate with: openssl rand -hex 32>
ACCESS_TOKEN_EXPIRE_MINUTES=30
REFRESH_TOKEN_EXPIRE_MINUTES=10080

# Auth
REQUIRE_INVITE_CODE=true  # Set false to allow open registration

Railway-specific (production):

ALLOW_RAILWAY_ORIGINS=true  # Enables CORS for PR environments

Frontend (.env.local - optional)

VITE_API_URL=http://localhost:8000  # Override API URL

Development Commands

Start Development Environment

# 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

Run Tests

# 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

Frontend Operations

# 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

# From project root
cd backend
pip install httpx  # Required for seed scripts
python -m scripts.seed_trees

Database Operations

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

CORS: Include Both allow_origins AND allow_origin_regex

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

findNode Requires Tree Structure Parameter

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

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

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

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:

// 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(...)
<div className={cn('...', scratchpadOpen && 'pr-[440px]')}>
  <div className="mx-auto max-w-4xl">  {/* 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

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 API Client Module (Frontend)

  1. Create types in frontend/src/types/modulename.ts
  2. Export types from frontend/src/types/index.ts
  3. Create API client in frontend/src/api/modulename.ts following existing pattern
  4. Export from frontend/src/api/index.ts
  5. Import in components: import { moduleApi } from '@/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>
  • 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-namegh 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)

  • Step Categories database and API
  • Step Library database schema (step_library, step_ratings, step_usage_log)
  • Step Library CRUD API with search and ratings
  • User Preferences (Settings page, export format default)
  • 🔲 Step Library browser component (frontend)
  • 🔲 Add Custom Step button in tree navigation
  • 🔲 Custom step creation modal
  • 🔲 Personal tree branching (add custom steps during sessions)
  • 🔲 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)

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
Bug Fixes LESSONS-LEARNED.md
Feature Specs 04-FEATURE-SPECIFICATIONS.md
Phase 2.5 Spec PHASE-2.5-PERSONAL-BRANCHING.md
Rebrand Guide REBRAND-IMPLEMENTATION-GUIDE.md

Railway Deployment

Production

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 (available in PR environments):

  • /debug/cors - Check CORS configuration (allow_railway_origins, cors_mode)

Railway CLI (Local)

The Railway CLI can be installed locally for managing deployments, viewing logs, and checking service status.

Setup on a new machine:

# 1. Install Railway CLI via npm (in project root)
cd C:\Dev\Projects\patherly
npm init -y
npm install @railway/cli

# 2. Login to Railway
./node_modules/.bin/railway login

# 3. Link to the project
./node_modules/.bin/railway link
# Select: selfless-grace → production

Common Commands:

# Check service status
./node_modules/.bin/railway service status --service patherly
./node_modules/.bin/railway service status --all

# View logs
./node_modules/.bin/railway service logs --service patherly

# Check environment variables
./node_modules/.bin/railway variables --service patherly

# Deploy new code (from backend directory)
cd backend && ../node_modules/.bin/railway up --service patherly

# Redeploy existing build
./node_modules/.bin/railway service redeploy --service patherly --yes

Services:

  • patherly - Backend API
  • hopeful-liberation - Frontend
  • Postgres - Database

Note: The node_modules/, package.json, and package-lock.json files for the Railway CLI are gitignored (local tooling only).


Contact

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