Remove outdated purple gradient/theme toggle references, add monochrome design system patterns and component guidelines for future sessions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
37 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 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: Monochrome — pure black (
#000) backgrounds, white text with opacity levels (PR #49) - Fonts: Inter (all text) — loaded via Google Fonts
- Logo: Inline SVG in
BrandLogo.tsx(decision-tree icon, white fill) - Brand assets:
brand-assets/(source SVGs),frontend/src/assets/brand/(app assets),frontend/public/icons/(favicon) - CSS utilities:
glass-card,glass-card-hover,glass-card-glow,glass-stat(defined inindex.css) - Design system guide:
docs/plans/Frontend/DESIGN_SYSTEM_GUIDE.md - Rebrand guide: REBRAND-IMPLEMENTATION-GUIDE.md
When adding new frontend pages or components, use "ResolutionFlow" for any user-visible branding. Follow the monochrome design system: black backgrounds, glass-card for containers, text-white with opacity variants for text hierarchy, white primary buttons, functional color only for status indicators.
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-only monochrome design (theme toggle removed in PR #49)
- 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)
- Settings page at
- 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_librarytable for reusable troubleshooting stepsstep_ratingstable for user ratings/reviewsstep_usage_logtable 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
- Full CRUD at
- Frontend Rebrand (PR #26):
- Renamed from "Patherly" to "ResolutionFlow" in all user-facing UI
- Custom SVG logo in header and auth pages
- Updated favicon and browser tab title
- Monochrome Design System (PR #49):
- Dark-only mode, theme toggle removed
- Glass-morphism cards (
glass-card,glass-card-glowCSS utilities) - 84 files migrated from themed/colored to monochrome
- CSS variables remapped to monochrome, Tailwind config simplified (single font: Inter)
- Functional color (green/red/yellow/blue) reserved for status indicators only
- Token Refresh Fix:
- Silent refresh with single-flight queue (prevents concurrent 401 race conditions)
- Backend
get_refresh_token_payloaddependency extracts refresh token from Authorization header - Frontend Axios interceptor queues failed requests during refresh, retries after success
- Auth store synced after silent refresh via
setTokensaction
- RBAC & Permissions:
is_super_adminboolean on User model (migration 010)- Role hierarchy: super_admin > team_admin > engineer > viewer
rolefield values: 'engineer' | 'viewer' (no more 'admin')- Team Admin =
role='engineer'+is_team_admin=True+ validteam_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 toengineer - HTML export XSS fix — all user content escaped via
html.escape() - Secret key validator — rejects default key when
DEBUG=False - Role CHECK constraint on
userstable (migration 011) test_adminfixture fixed to properly grantis_super_admin=True
- Registration role field removed from
- Security Hardening (Phase B):
- B1: Tree access check on
start_sessionviacan_access_treefrom permissions.py - B2: All inline permission helpers replaced with centralized imports from
permissions.py - B3:
require_engineer_or_adminchecksis_team_adminbefore role check - B4:
is_activefield on User model (migration 012), enforced inget_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(notget_current_user)
- B1: Tree access check on
- Permissions UX (Phase C):
- Super admin bypass in tree list filter (
build_tree_access_filterreturnssa_true()) - Audit log table (
audit_logs) with JSONB details, integrated at admin + tree delete endpoints - Soft delete for trees (
deleted_at+deleted_bycolumns, migration 015) - ProtectedRoute supports optional
requiredRoleprop for role-based route guards - TreeEditorPage checks
canEditTree()after fetch, before loading into editor - Reusable
ConfirmDialogcomponent + tree delete UI on library page - CustomStepModal hides "Type My Own" tab for users without
canCreateSteps
- Super admin bypass in tree list filter (
- 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/corsgated behindif 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) - Monochrome colors using CSS variables
- 6px thin scrollbars site-wide (Firefox
- Admin Panel (Feb 2026):
- Full admin panel at
/admin/*with 8 pages (dashboard, users, invite codes, audit logs, plan limits, feature flags, settings, categories) - Super admin access: requires
is_super_admin=trueon User model - Admin API endpoints:
/api/v1/admin/*(all requirerequire_admindependency) - Utility script:
backend/make_superadmin_simple.py list|<email>- promote users to super admin - ActionMenu component: uses React Portal to avoid overflow clipping in tables
- API Path Gotcha: Frontend
apiClientbaseURL ishttp://localhost:8000/api/v1— all API calls should use relative paths WITHOUT/api/v1/prefix (e.g.,/admin/usersnot/api/v1/admin/users)
- Full admin panel at
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 — monochrome glass-morphism design (dark-only)
- Fonts: Inter (all text) 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 # Unused (dark-only mode, no toggle)
│ │ │ ├── treeEditorStore.ts # Tree editor state (immer + zundo)
│ │ │ └── userPreferencesStore.ts # User preferences (NEW)
│ │ ├── components/
│ │ │ ├── common/ # Modal, ErrorBoundary, ConfirmDialog, BrandLogo
│ │ │ ├── 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-<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=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):
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
- Frontend: http://localhost:5173
- Backend API: http://localhost:8000
- API Docs: http://localhost:8000/api/docs
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 --override-ini="addopts="
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(notdecision_tree) - Update
.envif you see the old name
Virtual Environment
- Always check for
(venv)prefix before running pip - Don't use
--break-system-packageswhen 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.
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:
# 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
// 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.
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_adminreplaces allrole == "admin"checks. Never userole == "admin". - Frontend: use
usePermissions()hook for all role/permission checks TreeListItemincludesteam_idfor frontend permission checks (author_idandteam_idare 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
// 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:
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 - 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/solutiontypes withchildren[]). Seetypes/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 (includessetTokensfor silent refresh sync) - Theme: Removed — dark-only mode (no theme toggle)
- Tree Editor:
useTreeEditorStore- Zustand + immer + zundo (undo/redo) - User Preferences:
useUserPreferencesStore- Zustand with localStorage persistence (export format default)
Component Guidelines
- Use
cn()from@/lib/utilsfor Tailwind class merging - Use Lucide icons (no
titleprop - 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)
Monochrome Design System Patterns
- Backgrounds: Pure black (
bg-black), subtle radial gradients for depth - Cards:
glass-card rounded-2xl(transparent gradient + backdrop-blur), NOTbg-card border-border - Buttons: Primary:
bg-white text-black hover:bg-white/90. Secondary:border border-white/10 text-white/60 hover:bg-white/10 - Inputs:
border-white/10 bg-black/50 text-white+ focus:border-white/30 ring-white/20 - Text hierarchy:
text-white→text-white/70→text-white/40→text-white/30 - Borders/dividers:
border-white/[0.06]orborder-white/10 - Functional color only: emerald-400 (success), red-400 (error), yellow-400 (warning), blue-400 (info)
- Reference:
docs/plans/Frontend/DESIGN_SYSTEM_GUIDE.mdanddocs/plans/Frontend/COMPONENT_EXAMPLES.md
TypeScript Type Organization
- New type modules: Create in
types/directory (e.g.,types/step.ts) - Export from
types/index.tswithexport * 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
- New endpoint: Create in
endpoints/→ add torouter.py→ schema inschemas/→ tests → frontend API client - New page: Create in
pages/→ add route inrouter.tsx→ nav link inAppLayout.tsx - Schema change: Update model →
alembic revision --autogenerate -m "desc"→ review →alembic upgrade head - New frontend API module: Types in
types/→ export fromtypes/index.ts→ client inapi/→ export fromapi/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
constoverlet - 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 <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-name→gh pr create
Commit Strategy for Large Features
- Break implementation into logical phases (foundation → components → integration)
- Commit after each phase with
npm run buildvalidation - 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
- Check Docker:
docker ps- ispatherly_postgresrunning? - Check
.env- is DATABASE_URL correct (patherlynotdecision_tree)? - Check venv: is
(venv)prefix showing?
Frontend compile errors
- Check
frontend/src/lib/utils.tsexists (providescn()function) - Run
npm installto ensure dependencies are installed
Tests failing
- Create test database:
docker exec -it patherly_postgres psql -U postgres -c "CREATE DATABASE patherly_test;" - Install dev deps:
pip install -r requirements-dev.txt - Ensure pytest-asyncio version:
pip install pytest-asyncio==0.24.0 - If
unrecognized arguments: --coverror: run with--override-ini="addopts="or installpytest-cov
API 500 errors
- Check server logs for datetime errors (timezone issue)
- 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
- Frontend: https://resolutionflow.com
- Backend: https://api.resolutionflow.com
- Database: Railway-managed PostgreSQL
- Deploys automatically on push to
main
PR Environments
Railway creates isolated preview environments for each pull request.
Workflow:
- Create feature branch:
git checkout -b feature/my-feature - Make changes, commit, push to origin
- Open Pull Request on GitHub
- Railway auto-creates preview environment (backend + frontend + DB)
- Generate domains manually in Railway dashboard:
- Switch to the PR environment
- Click on each service → Settings → Networking → Generate Domain
- Set
VITE_API_URLon frontend service to point to the PR backend URL- IMPORTANT: Must include
https://prefix (e.g.,https://patherly-patherly-pr-24.up.railway.app)
- IMPORTANT: Must include
- Redeploy frontend if needed
- Test at preview URLs
- Merge PR → auto-deploys to production
- Railway cleans up PR environment after merge
Environment Variables:
- PR environments inherit from
productionbase environment REQUIRE_INVITE_CODE=trueis inherited (create invite codes in PR DB if needed)DATABASE_URLis auto-provided for isolated PR databaseALLOW_RAILWAY_ORIGINS=true(shared variable) - enables CORS for all*.up.railway.apporigins
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 (requiresDEBUG=Truein environment)
Railway CLI (Local)
Railway CLI installed via npm in project root (gitignored). Services: patherly (backend), hopeful-liberation (frontend), Postgres (DB).
# 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