Race condition: on page remount, selectChat(oldId) loads session data async. If the user clicks New Chat before the API returns, the old session's pending_task_lane was being applied to the new session's state, showing stale tasks and blocking new ones from appearing. Fix: currentChatRef tracks the most recently requested chat ID synchronously. All chat-creation paths (selectChat, handleNewChat, handleResumeNew) update it immediately. After each await in selectChat, bail if the ref no longer matches. Also documents the pattern as Lesson 106 in CLAUDE.md for future reference. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
39 KiB
CLAUDE.md - Patherly / ResolutionFlow Project Context
Last Updated: March 27, 2026
Project Overview
Patherly (user-facing brand: ResolutionFlow) is a SaaS product for MSP professionals. It provides troubleshooting decision trees that guide engineers through proven troubleshooting paths, capture decisions and notes, and generate professional ticket documentation.
Target Market: MSP companies — IT service providers managing infrastructure and support for multiple clients.
SaaS Context: Multi-tenant design — teams represent MSP companies, trees shared within teams, tiered access (super_admin, team_admin, engineer, viewer).
Branding
| Context | Name Used |
|---|---|
| Repository / directory / database / Docker | patherly / patherly_postgres |
| Backend, frontend UI, production URLs | ResolutionFlow |
- Design system: DESIGN-SYSTEM.md — THE source of truth for all design decisions
- Design aesthetic: Flat, high-contrast dark theme (Sentry/PostHog-inspired). No glass morphism, no gradients on surfaces, no ambient effects. Light mode planned.
- Accent color: Electric blue (#60a5fa dark / #2563eb light). Used sparingly — ≤5% of the UI. Warning is amber (#fbbf24), info is cyan (#67e8f9).
- Fonts: IBM Plex Sans (
font-sans, body), Bricolage Grotesque (font-heading, headings), JetBrains Mono (font-mono, code) — loaded via Google Fonts - Logo: 30px gradient square (ember orange) + "ResolutionFlow" in Bricolage Grotesque 700
- Layout: Icon rail sidebar (72px default) with hover flyout panels. Pinnable to full 260px sidebar. See DESIGN-SYSTEM.md
- Brand assets:
brand-assets/(source SVGs),frontend/src/assets/brand/(app assets),frontend/public/icons/(favicon) - Terminology: User-facing label is "Flows" (not "Trees"). Procedural flows are called "Projects" in the UI. Step Library is called "Solutions Library" in the UI. Maintenance flows are hidden from UI for pilot (backend still supports them).
tree_typecolumn values unchanged in DB. - Reference mockups:
docs/mockups/(HTML files, open in browser)
Component styling: See Design System section below and DESIGN-SYSTEM.md. All colors via CSS variables. Use "Flows" not "Trees" in user-facing text; use "Projects" not "Procedures" for procedural flows.
Implementation Principles
- Prefer correct architecture over minimal diff
- If two approaches exist, implement the one that scales, not the one that's faster to write
- Flag any "simpler approach" tradeoffs for product owner review before proceeding
Current State
- Phase: Go-to-Market Validation (Pre-PMF)
- Backend: Complete (55+ API endpoints, 100+ integration tests)
- Frontend: Core features complete, Tree Editor functional
- Database: PostgreSQL with Docker, 98 migrations
- Detailed status: CURRENT-STATE.md
What's In Progress
- GTM validation: Shadow & Ship — founder dogfooding for 2 weeks, then 5 colleague pilot
- Solutions Library spec written (
docs/plans/2026-03-23-solutions-library-design.md), implementation post-pilot - Remaining open issues: #66 Templates + Import/Export, #60 Recurring Issue Detection, #58 Step Feedback Flag
Tech Stack
Backend
- Framework: Python FastAPI
- Database: PostgreSQL 16 (async via SQLAlchemy 2.0 + asyncpg)
- Migrations: Alembic
- Auth: JWT (python-jose) + bcrypt, refresh token rotation (JTI-based)
- Validation: Pydantic v2
- Scheduling: APScheduler 3.x (async, in-process with FastAPI lifespan) + croniter + pytz
Frontend
- Framework: React 19 + Vite + TypeScript
- Styling: Tailwind CSS v4 (
@tailwindcss/viteplugin, CSS-only config inindex.css) — flat dark theme with ember orange accent (see DESIGN-SYSTEM.md) - State: Zustand (with immer + zundo for undo/redo)
- Routing: React Router v7
- API Client: Axios with token refresh interceptor
- Icons: Lucide React
Key Project Structure
patherly/
├── backend/
│ ├── app/
│ │ ├── main.py # FastAPI entry point
│ │ ├── api/endpoints/ # Route handlers (auth, trees, sessions, admin, steps, survey, copilot, assistant_chat, integrations)
│ │ │ ├── flow_proposals.py # Knowledge Flywheel review queue CRUD
│ │ │ └── flowpilot_analytics.py # FlowPilot dashboard metrics
│ │ ├── api/deps.py # Auth dependencies (includes require_team_admin)
│ │ ├── api/router.py # Route registration
│ │ ├── core/ # config, database, permissions, security, audit, rate_limit
│ │ ├── models/ # SQLAlchemy models (includes FlowProposal)
│ │ ├── schemas/ # Pydantic schemas
│ │ ├── services/psa/ # PSA provider abstraction (base, connectwise/, autotask/, halopsa/, cache, encryption, registry, types)
│ │ ├── services/knowledge_flywheel.py # AI session analysis → flow proposals
│ │ ├── services/knowledge_flywheel_scheduler.py # APScheduler job for batch analysis
│ │ └── services/knowledge_gap_service.py # Weak options & escalation signal detection
│ ├── alembic/ # Database migrations (001-029+)
│ ├── scripts/ # seed_data.py, seed_trees.py
│ └── tests/ # pytest integration tests
├── frontend/
│ ├── src/
│ │ ├── api/ # Axios client + endpoint modules
│ │ ├── components/ # common, layout, dashboard, tree-editor, session, procedural, procedural-editor, library, step-library, ui, flowpilot
│ │ ├── hooks/ # usePermissions, useSessionTimer, useKeyboardShortcuts
│ │ ├── pages/ # All page components
│ │ ├── store/ # Zustand stores (auth, treeEditor, proceduralEditor, userPreferences, scriptGeneratorStore)
│ │ └── types/ # TypeScript interfaces
│ └── (Tailwind v4: CSS-only config in src/index.css)
├── docs/plans/archive/ # Archived design/impl docs (pre-March 2026)
├── CLAUDE.md # This file
├── CURRENT-STATE.md # Detailed feature status
├── LESSONS-LEARNED.md # (Deprecated — consolidated into CLAUDE.md)
└── docs/plans/ # Design docs & implementation plans
Environment Variables
Backend (backend/.env)
APP_NAME=ResolutionFlow
DEBUG=true
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/patherly
DATABASE_URL_SYNC=postgresql://postgres:postgres@localhost:5432/patherly
SECRET_KEY=<openssl rand -hex 32>
ACCESS_TOKEN_EXPIRE_MINUTES=5
REFRESH_TOKEN_EXPIRE_DAYS=7
REQUIRE_INVITE_CODE=true
Frontend (frontend/.env.local - optional)
VITE_API_URL=http://localhost:8000
ConnectWise PSA Integration
ResolutionFlow integrates with ConnectWise PSA (formerly Manage) as the primary PSA integration. All ConnectWise API reference materials live in docs/connectwise/.
Best Practices Documentation
Official ConnectWise developer guides live in docs/connectwise/best-practices/. Read these BEFORE implementing any CW API integration code:
PSA-API-Requests.md— HTTP methods, response codes, condition query syntax, PATCH format, URL encoding, partial responses, custom fields. READ FIRST.PSA-Callbacks.md— Callback type/level matrix, retry behavior, URL parameter gotcha, HMAC signature verification.PSA-Pagination.md— Navigable vs Forward-Only pagination, Link headers, while-loop pattern.PSA-Service-Tickets.md— Ticket field philosophy, recommended field mappings.PSA-Versioning.md— Pin API version via Accept header. Useapplication/vnd.connectwise.com+json; version=2025.16.PSA-Cloud-URL-Formatting.md— Dynamic base URL construction via/login/companyinfo/{companyId}.Bundled-Requests.md— Batch multiple API calls into one request via/system/bundles.PSA-Markdown.md— Ticket notes support markdown. Format session documentation output accordingly.PSA-Company-Synchronization.md— Filter companies by Status/Type for mapping UI.PSA-Data-Protection.md— Security role model, request minimal permissions (MY not ALL).
Reference Files (read in this order)
docs/connectwise/CONNECTWISE-API-REFERENCE.md— Read FIRST. Quick reference covering auth patterns, tiered endpoint map, key field mappings, and integration architecture flows.docs/connectwise/connectwise-psa-resolutionflow-reference.json— Extracted OpenAPI 3.0.1 spec (v2025.16) with only the 670 endpoints and 342 schemas relevant to ResolutionFlow. Use for exact field types, request/response shapes, and parameter details.docs/connectwise/connectwise-psa-openapi-full.json— Complete ConnectWise PSA OpenAPI spec (1838 endpoints, 842 schemas). Only consult if you need an endpoint outside the extracted subset.
Integration Architecture
- Session → Ticket Notes: Post auto-generated session documentation to ConnectWise tickets as internal analysis notes via
POST /service/tickets/{id}/notes - Ticket Context → Session Runner: Pull ticket details, company info, and attached configurations to give FlowPilot AI real-world context
- Callbacks: Register webhooks via
/system/callbacksfor real-time ticket event notifications to suggest relevant Flows
Key Implementation Rules
- Auth: API Key auth (Base64 of
companyId+publicKey:privateKey) +clientIdheader on every request clientIdis server-side config (CW_CLIENT_IDinconfig.py) — identifies the ResolutionFlow app, NOT per-tenant. Per-connection credentials:company_id,public_key,private_key,server_url- All PSA integration code in
services/psa/— provider pattern withPSAProviderabstract base class,ConnectWiseProviderimplementation,PsaProviderRegistryfor multi-PSA dispatch - PSA endpoints in
api/endpoints/integrations.py— connection CRUD, ticket ops, member mapping - Credentials encrypted at rest via
services/psa/encryption.py(Fernet) - Each MSP tenant provides their own CW credentials — ResolutionFlow stores these per-team, never per-user
- Design for the Autotask integration following the same service layer pattern (future PSA)
- In-memory TTL cache in
services/psa/cache.pyfor board/status/priority lookups - Respect CW API: paginate with max 1000 per page, handle retries gracefully
Development Commands
# Start PostgreSQL
docker start patherly_postgres
# Backend (from backend/)
source venv/bin/activate # Linux/Mac
# .\venv\Scripts\Activate # Windows
uvicorn app.main:app --reload
# Frontend (from frontend/)
npm run dev
# Run tests (from backend/)
pytest --override-ini="addopts="
# First time only: create test database
docker exec -it patherly_postgres psql -U postgres -c "CREATE DATABASE patherly_test;"
# Frontend build (IMPORTANT: stricter than tsc --noEmit — always use as final check)
cd frontend && npm run build
# Database migrations
cd backend && alembic upgrade head
alembic revision --autogenerate -m "Description" --rev-id=NNN # NNN = next sequential number
# IMPORTANT: Migrations use sequential 3-digit IDs (001, 002, ..., 068, 069).
# Check the latest: ls backend/alembic/versions/ | grep -E '^\d{3}_' | sort | tail -1
# The revision ID and filename prefix MUST match (e.g., revision="068", file=068_description.py).
# down_revision MUST point to the previous sequential number. Never use hex hash IDs for new migrations.
# Access PostgreSQL
docker exec -it patherly_postgres psql -U postgres -d patherly
# Seed data
cd backend && pip install httpx && python -m scripts.seed_trees
# CI/CD debugging
gh run list --limit 5 # Recent CI runs
gh run view <id> --log-failed # Failed job logs
gh run view <id> --json jobs --jq '.jobs[] | {name: .name, conclusion: .conclusion}'
# NEVER use `gh run watch` — it holds context open and burns tokens while waiting
URLs
- Frontend: http://localhost:5173
- Backend API: http://localhost:8000
- API Docs: http://localhost:8000/api/docs
Test Users (seeded via scripts/seed_test_users.py)
- All share password:
TestPass123! admin@resolutionflow.example.com(super_admin),teamadmin@resolutionflow.example.com(team_admin),engineer@resolutionflow.example.com(engineer),pro@resolutionflow.example.com(solo pro)
Critical Lessons Learned
Lessons 1-40 archived to
docs/LESSONS-ARCHIVE.md— fixes are baked into the codebase. Consult if you hit a regression.
Active Lessons (41+)
41. Assistant chat uses local React state, not Zustand: AssistantChatPage.tsx uses useState for chats, messages, input, loading. No store.
42. Public pages use raw fetch(), not apiClient: Survey, shared sessions, and no-auth pages use fetch() with full URL. apiClient requires auth tokens.
43. Adding new email types: Add static async method to EmailService in core/email.py. Fire-and-forget from endpoints (log errors, don't fail).
44. AI Chat Builder is flow-type-aware: ai_chat_service.py dispatches by flow_type. Troubleshooting: [TREE_UPDATE] markers. Procedural: [STEPS_UPDATE] markers. Both support [METADATA].
45. Intake form field schema: Uses variable_name and field_type (NOT name and type).
46. CreateFlowDropdown uses AIPromptDialog: Opens prompt modal, starts AI session, generates flow, navigates to editor with { state: { aiPanelOpen: true, sessionId } }.
47. Editor-Embedded Flow Assist: EditorAIPanel (320px side panel) + useEditorAI hook. Ghost nodes use _suggestion: true flag. Actions route to model tiers via settings.get_model_for_action(). Delta responses use [DELTA]...[/DELTA] markers.
48. Tree orphan validation uses dynamic root ID: Orphan check compares against state.treeStructure?.id (NOT hardcoded 'root').
49. Full-stack features — verify both ends: Check the full data flow: schema → endpoint → API client → hook → store → UI.
50. Anthropic SDK retry: Set max_retries=1 to fail fast. Default max_retries=2 can take 3× timeout.
51. AI model tier routing: Use settings.get_model_for_action(action_type). Model IDs: use alias form (claude-sonnet-4-6).
52. Mobile scroll-to-top: Use ref.current.scrollIntoView(), not window.scrollTo(). Trigger via useEffect.
53. Flex height chain: Every ancestor must be a flex container for flex-1 to work. Missing flex class collapses React Flow to 0 height.
54. React Flow CSS in Tailwind v4: Import in index.css, not component JS. Override dark theme using --xy-* CSS custom properties.
55. App shell height chain: Every wrapper between .main-content and canvas needs flex + flex-1 + min-h-0 or h-full.
56. Railway backend service name is patherly: Production DB name is railway. Public Postgres proxy: interchange.proxy.rlwy.net:45797.
57. Node field priority: title → question → description → content → label. See copilot_service.py.
58. scriptGeneratorStore.generate() optional param: Always wrap: onClick={() => generate()}, never onClick={generate}.
59. ConnectWise clientId is server-side config: Set in config.py as CW_CLIENT_ID. Per-connection: company_id, public_key, private_key, server_url.
60. Dockerfile build args for Vite env vars: Any new VITE_* or VITE_PUBLIC_* env var must be added as ARG + ENV in frontend/Dockerfile for Railway deploys. Railway env vars are runtime-only unless explicitly passed through as Docker build args. Without this, import.meta.env.VITE_* resolves to undefined in production builds.
61. Procedural sessions auto-start on page load: ProceduralNavigationPage calls startSession() immediately in loadTree() — there is no intake form screen or "Start" button. Variables are filled inline during execution. Troubleshooting flows DO have a start screen with ticket/client fields. Don't write tests or UI that assume a Start button on procedural flows.
62. Playwright strict mode — scope selectors to avoid ambiguity: Step titles appear in both the sidebar checklist and main content heading. Use getByRole('heading', { name }) for the main content, or scope with page.locator('.animate-scale-in') for command palette items. getByText() frequently matches multiple elements due to the sidebar + main content layout.
63. Node 20 required for frontend builds: Vite 7+ requires Node 20.19+. The system Node may be v18; use nvm: export NVM_DIR="$HOME/.nvm" && source "$NVM_DIR/nvm.sh" && nvm use 20. For direct binary access without nvm sourcing: PATH="/home/michaelchihlas/.nvm/versions/node/v20.19.0/bin:$PATH".
64. PostHog product analytics: Initialized via PostHogProvider in main.tsx with explicit posthog.init() + client prop pattern. Event helpers in lib/analytics.ts — use analytics.eventName(props) to track. identifyUser() called in authStore.fetchUser(), resetAnalytics() on logout. Env vars: VITE_PUBLIC_POSTHOG_KEY, VITE_PUBLIC_POSTHOG_HOST. Autocapture enabled.
65. Local Docker Compose uses resolutionflow database on port 5433: Container name is resolutionflow_postgres, database is resolutionflow (not patherly), port mapped to 5433 (not 5432). The POSTGRES_PORT env var controls this. Playwright config defaults must match: postgresql+asyncpg://postgres:postgres@127.0.0.1:5433/resolutionflow.
66. Dev environment runs on Hostinger VPS (46.202.92.250), not localhost: Code-server runs in Docker on a VPS (previously devserver01/192.168.0.9). Frontend/backend are accessed via 46.202.92.250, not localhost. CORS must include the VPS IP in CORS_ORIGINS and FRONTEND_URL. Frontend .env must set VITE_API_URL to the VPS backend URL. See DEV-ENV.md for full setup, Docker config, networking, and known issues.
67. Tree editor route is /trees/new: NOT /editor/new. Check router.tsx line 156 for the canonical path. Use getTreeEditorPath() from @/lib/routing when navigating programmatically.
68. APScheduler jobs need max_instances=1: Without it, overlapping scheduler runs can process the same records twice (TOCTOU race). Always set max_instances=1 on interval jobs in main.py.
69. PostgreSQL func.sum(case(...)) returns Decimal via asyncpg: Cast to int() before storing in Pydantic dict[str, Any] fields, or JSON serialization may produce unexpected types.
70. Toast library uses toast.warning() not toast.warn(): Import from @/lib/toast. Methods: success, error, warning, info. See frontend/src/lib/toast.ts.
71. Enhancement/branch_addition proposals cannot be directly approved: Backend returns 400 — they require modified_flow_data via "Edit & Publish" flow. Only new_flow proposals support direct approve.
72. ai_sessions.status column is VARCHAR(30): Must fit requesting_escalation (23 chars). If adding new status values, verify length. Migration f0aad74ea51b widened from 20→30.
73. get_db rolls back on exception: The dependency does await session.rollback() on error to prevent InFailedSQLTransaction cascade. Never remove this — without it, one failed request poisons subsequent requests on the same connection.
74. FlowPilot action bar height chain: The action bar (Resolve/Escalate/Pause) requires every ancestor from app-shell grid down to have proper flex constraints. Key fix: ViewTransitionOutlet wrapper needs flex flex-col. If action bar disappears, check height chain with DevTools getBoundingClientRect() walk.
75. Dashboard prefill auto-submits: StartSessionInput navigates to /pilot or /assistant with { state: { prefill } }. FlowPilotSessionPage auto-submits via useEffect + prefillHandledRef guard — no double-enter. AssistantChatPage does the same pattern.
76. Active session navigation guard: FlowPilotSessionPage uses useBlocker (same as TreeEditorPage) to intercept navigation during active sessions. "Pause & Leave" auto-pauses before proceeding.
77. Prefer manual Alembic migrations for targeted changes: alembic revision --autogenerate picks up drift from all tables. For single-column fixes, use alembic revision -m "desc" and write op.alter_column() manually.
78. Landing page subtitle is "AI-Powered Troubleshooting for MSPs": Not "Decision Tree Platform". This tagline appears on login, register, and the HTML <title>. The old "Decision Tree Platform" was internal jargon misaligned with user-facing branding.
79. Custom modals must be mobile-responsive: Use items-end sm:items-center (bottom-sheet on mobile, centered on desktop) and max-w-full sm:max-w-lg (full-width on mobile). The shared Modal.tsx does this correctly — custom modal implementations must follow the same pattern. See PrepareSessionModal.tsx for the fix pattern.
80. TopBar search collapses to icon on mobile: Full search bar (hidden sm:block) shows on desktop; magnifying glass icon button (sm:hidden) shows on mobile (<640px). Both open the same CommandPalette. Don't add w-full search bar without the mobile icon fallback.
81. Never use transition: all in landing.css: Specify exact properties: transition: background 0.3s, border-color 0.3s, box-shadow 0.3s, transform 0.3s, opacity 0.3s. transition: all animates layout properties and causes jank.
82. bun requires PATH setup on devserver01: export BUN_INSTALL="$HOME/.bun" && export PATH="$BUN_INSTALL/bin:$PATH". The gstack browse binary and Playwright need this. Chromium system deps: libatk1.0-0 libatk-bridge2.0-0 libcups2 libxkbcommon0 libatspi2.0-0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2.
83. FlowPilot ActionBar is position: fixed; bottom: 0: Any UI element placed in normal document flow below the session content will be hidden behind it. New fixed-position elements (like the message bar) must use bottom: 68px (action bar height) and the same left: var(--sidebar-w) pattern. The conversation column uses pb-32 for clearance.
84. AI session abandoned status is fully wired: POST /ai-sessions/{id}/abandon sets status to abandoned with optional reason param. Frontend: aiSessionsApi.abandonSession(), useFlowPilotSession().abandonSession(), "Close" button in FlowPilotActionBar. Redirects to /sessions after closing.
85. Date range filter end dates must use end-of-day: toDate.toISOString() sends midnight (start of day), excluding items created later that day. Always set toDate.setHours(23, 59, 59, 999) before sending. For string-based date inputs (AI sessions), append T23:59:59.999Z. See SessionHistoryPage.tsx.
86. Script Builder system: AI-powered script generation at /script-builder. Chat-style interface generates PowerShell/Bash/Python scripts from natural language. Backend: ScriptBuilderSession model, script_builder_service.py, endpoints at /scripts/builder/. Frontend: ScriptBuilderPage, ScriptCodeBlock, ScriptPreviewModal, SaveToLibraryDialog. FlowPilot can hand off to Script Builder via action_type: "open_script_builder" with sessionStorage context passing.
87. FlowPilot must ask GUI vs script preference: When a task can be done via GUI or script (e.g., creating AD users), FlowPilot must ask the engineer which approach they prefer BEFORE suggesting either. Never assume the user wants a script. See FLOWPILOT_SYSTEM_PROMPT rules in flowpilot_engine.py.
88. Charcoal palette — sidebar-darkest approach: Sidebar #0e1016, page #16181f, cards #1e2028, borders #2a2e3a. This gives more contrast range than true-dark. All colors via CSS variables in index.css @theme block. Accent is electric blue (#60a5fa), not orange or cyan.
92. tsc -b in Dockerfile is stricter than npx tsc --noEmit: The production build (tsc -b && vite build) enforces noUnusedLocals and noUnusedParameters as hard errors. After any refactor that moves logic between components or removes features, trace every import and destructured prop to remove orphans. IDE warnings (yellow squiggles) flag these — check them before pushing.
93. FlowPilot actions live in the page header, not a bottom bar: FlowPilotSessionPage renders Resolve/Escalate/Share Update in the header bar. Desktop: inline buttons + ⋯ overflow (Pause/Close). Mobile: single ⋯ menu. The bottom only has the message input. FlowPilotActionBar component still exists but is no longer used in the main session flow.
94. Frontend chat uses unified_chat_service, not assistant_chat_service: AssistantChatPage calls /ai-sessions/{id}/chat → unified_chat_service.py. The old assistant_chat_service endpoints were removed (only retention settings remain at /assistant/retention). When tracing chat features, start from aiSessionsApi.sendChatMessage → ai_sessions.py → unified_chat_service.py. Never wire chat features into assistant_chat.py.
95. Image upload → AI vision pipeline: Paste/attach images → upload to Railway S3 bucket via uploadsApi.upload() → send upload_ids with chat message → backend fetches from S3 via storage_service.download_file() → resized via storage_service.resize_image_for_vision() (Pillow, 1568px max, PNG→JPEG) → base64-encoded → sent as Claude multimodal content blocks. Max 3 images/message. Images are NOT stored in conversation history (text-only). Vision helpers live in storage_service.py.
96. bg-accent is ember orange — never use for code/kbd elements: In Tailwind v4, bg-accent maps to --color-accent: #f97316. Use bg-code for code blocks, bg-white/[0.12] border border-white/[0.06] for inline code/badges, bg-white/[0.08] for kbd shortcuts. Orange is reserved for interactive elements only (buttons, active nav, links).
97. Railway Object Storage (S3 bucket) is provisioned: Bucket resolutionflow-uploads on Railway canvas. Variables: STORAGE_ENDPOINT, STORAGE_ACCESS_KEY, STORAGE_SECRET_KEY, STORAGE_BUCKET_NAME, STORAGE_REGION — mapped via variable references on the patherly backend service. Accessed via boto3 in storage_service.py. Pillow (Pillow>=10.0.0) + libjpeg-dev/zlib1g-dev in Dockerfile for image resize.
98. lazyWithRetry for stale chunk errors: All lazy-loaded routes use lazyWithRetry from @/lib/lazyWithRetry.ts instead of React.lazy. Auto-reloads the page on chunk load failures (stale deploys). Uses sessionStorage debounce (10s) to prevent loops. When adding new lazy routes, use lazyWithRetry, not lazy.
99. Tailwind v4 text-secondary renders invisible on dark backgrounds: text-secondary maps to --color-secondary: #2e3140 (a dark surface color), NOT --color-text-secondary. For readable secondary text, use text-muted-foreground (#848b9b). Also avoid text-muted (#4f5666) for body text — it's for labels only. This applies to ALL new components.
100. Hover pop-out card pattern: For cards that expand on hover "in front of everything": use pointer-events-none on the scrim (fixed inset-0 z-40 bg-black/30), absolute-position the expanded card at z-50 with its own onClick handler, and dismiss via onMouseLeave on the wrapper div. Never put interactive event handlers on the scrim — it blocks clicks on sibling elements.
101. AI marker format compliance: The AI assistant uses [QUESTIONS], [ACTIONS], and [FORK] markers in responses. Parsed by unified_chat_service.py (_parse_*_marker functions), returned as structured data in the API response. System prompt in assistant_chat_service.py has a final reminder section, and each user message gets an invisible [SYSTEM: ...] reminder appended in _call_anthropic_cached(). If markers stop appearing: check conversation history stores display_content (stripped), verify system prompt final reminder exists, check user message reminder injection is active.
102. TaskLane activation must happen in ALL chat response paths: AssistantChatPage.tsx has three code paths calling sendChatMessage: handleSend (regular messages), sendPrefill (dashboard handoff), handleResumeNew (resume from concluded session). ALL three must check response.actions/response.questions and call setShowTaskLane(true). Missing this in any path causes TaskLane to not appear on first message.
103. Docker not available in code-server container: The dev environment runs code-server inside Docker on the VPS. The docker CLI is not available inside the code-server container. To query the database, use the VPS SSH session: docker exec resolutionflow_postgres psql -U postgres -d resolutionflow -t -c "SQL". Python is also not available in the container.
104. landing.css uses self-contained --lp-* color variables: The landing page defines its own color palette at the top of landing.css (--lp-bg, --lp-accent, --lp-text-*, etc.). Never use var(--color-*) theme tokens in landing.css — they may resolve incorrectly outside the app shell context. Extend the --lp-* palette for any new landing page colors.
105. npm run build fails with EACCES: permission denied on dist/ in code-server: This is a filesystem permission issue in the Docker environment, not a TypeScript error — the TS compilation completes successfully. Use npx tsc -b to verify TypeScript cleanly without needing to write to dist/.
106. Guard async "select item → load data → apply state" flows with a ref: When a component lets the user switch between items (chat sessions, flows, scripts) and loads data asynchronously on each switch, the load for item A can complete after the user has already switched to item B — overwriting B's state with A's stale data. Fix pattern: keep a currentSelectionRef = useRef(initialId) and update it synchronously whenever the selection changes (in every creation/switch path). After every await, bail out if currentSelectionRef.current !== thisItemId. See AssistantChatPage.tsx selectChat for the reference implementation (currentChatRef).
RBAC & Permissions
- Role hierarchy: super_admin > team_admin > engineer > viewer
- Team Admin:
role='engineer'+is_team_admin=True+ validteam_id - Backend deps:
get_current_active_user(user, db)(any active + auto-downgrades expired trials),require_engineer_or_admin(blocks viewers),require_admin(super admin only) - Never use
role == "admin"— useis_super_admininstead - Frontend:
usePermissions()hook for all permission checks - Centralized:
backend/app/core/permissions.py,frontend/src/hooks/usePermissions.ts
Design System
Source of truth: DESIGN-SYSTEM.md — always read this before making visual or UI decisions.
- Theme: Flat, high-contrast dark theme (Sentry/PostHog-inspired). No glass morphism, no backdrop blur, no ambient orbs, no gradient backgrounds on surfaces. Light mode planned.
- Backgrounds:
bg-page(#1a1c23),bg-sidebar(#10121a),bg-card(#22252e),bg-elevated(#2e3140) - Cards:
bg-cardwith 1pxborder-default(#2e3240), 8px radius. No shadows, no blur, no gradients. Hover:border-hover(#3d4252) - Buttons: Primary: solid
accent(#f97316), white text, 5px radius. Ghost: transparent + 1px border, hoverbg-elevated - Inputs:
bg-input(#282b35) with 1pxborder-default, 5px radius. Focus:border-color: accent+box-shadow: 0 0 0 2px accent-dim - Text:
text-heading(#f0f2f5) →text-primary(#e2e5eb) →text-muted-foreground(#848b9b) →text-muted(#4f5666). NEVER usetext-secondary— in Tailwind v4 it maps to a surface color (#2e3140), not a text color. - Borders:
border-default(#2e3240),border-hover(#3d4252) - Functional colors:
#34d399(success),#eab308(warning),#f87171(danger) — each with-dimvariant at 10% opacity - Accent: Ember orange
#f97316— used sparingly (≤5% of UI).accent-dim=rgba(249,115,22,0.10),accent-text=#fdba74 - Deprecated: Do NOT use
glass-card,glass-stat,bg-gradient-brand,text-gradient-brand,backdrop-filter: blur(), ambient orbs, purple gradients, or cyan accent (#22d3ee)
Frontend Patterns
- Component guidelines: Use
cn()from@/lib/utils, Lucide icons (wrap in<span>for title), modals with fixed header/footer - Type organization: Create in
types/, export fromtypes/index.ts, import withimport type { T } from '@/types' - Scratchpad overlay:
position: fixed,onOpenChangecallback for parent padding adjustment,right-2positioning - Custom step flow:
CustomStepModal→PostStepActionModal→ContinuationModal→ custom step view. Key state:pendingStep,pendingContinuationNodeId,customBranchMode,branchOriginNodeId. UsefindCustomStep()notfindNode()for custom step UUIDs. - Session sharing:
ShareSessionModalmanages share links,SharedSessionPagerenders public/account views. Helper utils inlib/sessionShare.ts. Share URLs use/shared/sessions/:token. - Procedural navigation:
ProceduralNavigationPagehandles intake forms, step-by-step execution, and resume vialocation.state.sessionId. UsesStepChecklist,StepDetail,ProgressBar,CompletionSummarycomponents. - Routing helper: Use
getTreeNavigatePath()andgetTreeEditorPath()from@/lib/routingfor all tree/session navigation. - Account section layout:
AccountLayouthas NO sidebar nav. Account sub-pages (categories, target-lists) are reached via link cards onAccountSettingsPage.tsx. New account pages: add route inrouter.tsxunderaccountchildren + add a link card inAccountSettingsPage. - Dashboard cockpit:
QuickStartPageis the copilot-first launchpad. Greeting + "What are you troubleshooting?" + ChatGPT-styleStartSessionInput(auto-growing textarea, paste images, drag-drop files, attach button, paste logs, suggestion chips). Below:PendingEscalations,ActiveFlowPilotSessions,RecentFlowPilotSessions. Collapsible "Dashboard" section forPerformanceCards,KnowledgeBaseCards,TeamSummary. - Sidebar sections: Amber "New Session" button → Home → RESOLVE (History) → KNOWLEDGE (Flows with Solutions Library sub-item, Scripts) → INSIGHTS (Data). Footer: Account, Pin/Unpin. No help/guides/feedback in sidebar — accessible via TopBar.
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 - New public route (no auth): Add at top level in
router.tsxalongside/login,/register— NOT inside theProtectedRoute/AppLayoutchildren. - Schema change: Update model →
alembic revision --autogenerate -m "desc" --rev-id=NNN(NNN = next sequential number, e.g., 068 → 069) → review →alembic upgrade head - New frontend API module: Types in
types/→ export fromtypes/index.ts→ client inapi/→ export fromapi/index.ts
Coding Standards
Python
- Type hints everywhere, async/await for DB, Pydantic for validation,
DateTime(timezone=True)always
TypeScript
- Interfaces for all data,
constoverlet, functional components + hooks, reusable logic in custom hooks
Git
- Format:
type: description(feat, fix, refactor, docs, test, chore) - Always include
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> - Always create feature branch BEFORE committing:
git checkout -b feat/feature-name - Large features: commit per phase with
npm run buildvalidation
After Completing Work
When a feature, fix, or significant piece of work is finished and merged/committed:
- Update
CURRENT-STATE.md— move completed items, update "In Progress" and "What's Next" sections - Update
03-DEVELOPMENT-ROADMAP.md— check off completed work, update phase status - Close related GitHub Issues — use
gh issue close #Nfor any issues resolved by the work - Update
CLAUDE.mdif the work introduced new patterns, lessons learned, or changed project structure
gstack (Browser & Workflow Skills)
Web browsing: Always use the /browse skill from gstack for all web browsing needs. Never use mcp__claude-in-chrome__* tools.
Available skills:
| Skill | Purpose |
|---|---|
/office-hours |
Brainstorm new ideas (YC-style office hours) |
/plan-ceo-review |
CEO/founder-mode plan review (scope, ambition) |
/plan-eng-review |
Engineering plan review (architecture, edge cases) |
/plan-design-review |
Design plan review (UI/UX critique) |
/design-consultation |
Create a design system / DESIGN.md |
/review |
Pre-landing PR code review |
/ship |
Ship workflow (tests, review, PR creation) |
/browse |
Headless browser for QA testing and site dogfooding |
/qa |
Systematic QA testing + auto-fix bugs found |
/qa-only |
QA report only (no fixes) |
/design-review |
Visual QA — find and fix design inconsistencies |
/setup-browser-cookies |
Import cookies from real browser for authenticated testing |
/retro |
Weekly engineering retrospective |
/investigate |
Systematic debugging with root cause analysis |
/document-release |
Post-ship documentation updates |
/codex |
Second opinion via OpenAI Codex CLI |
/careful |
Safety guardrails for destructive commands |
/freeze |
Restrict edits to a specific directory |
/guard |
Full safety mode (careful + freeze) |
/unfreeze |
Remove edit restrictions |
/gstack-upgrade |
Upgrade gstack to latest version |
Deployment (Railway)
- Production:
resolutionflow.com(frontend),api.resolutionflow.com(backend) - Auto-deploys on push to
main - PR environments auto-created (need manual domain generation in Railway dashboard)
- PR envs need
VITE_API_URLset withhttps://prefix on frontend service ALLOW_RAILWAY_ORIGINS=trueenables CORS for*.up.railway.app- Shared Variables (project-level in Railway dashboard) auto-propagate to all environments including PR envs — use for secrets like
ANTHROPIC_API_KEY - Super admin utility:
backend/make_superadmin_simple.py list|<email>
Future Roadmap
- Phase 3: PSA integrations (ConnectWise in progress), file attachments, client context, analytics
- Phase 4: Additional PSA integrations (Autotask/Kaseya), PowerShell automation, enterprise SSO
Quick Reference
| What | Where |
|---|---|
| API Docs | http://localhost:8000/api/docs |
| Detailed Status | CURRENT-STATE.md |
| Development Roadmap | 03-DEVELOPMENT-ROADMAP.md |
| GitHub Issues | gh issue list --state open |
| Bugs & Fixes | CLAUDE.md → Critical Lessons Learned section |
| Design System | DESIGN-SYSTEM.md |
| Dev Environment | DEV-ENV.md — 46.202.92.250 setup, Docker, CORS, networking |