Add CW security roles reference docs and PSA ticket management plan. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9.5 KiB
Lessons Archive (1-70)
These lessons were originally in CLAUDE.md. They've been archived because the fixes are now baked into the codebase. Consult this file if you encounter a regression in any of these areas.
1. DateTime Handling — Always timezone-aware: Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)). NEVER use datetime.utcnow().
2. SQLAlchemy Async — No lazy loading on new objects: Use direct SQL for junction tables: await db.execute(tree_tag_assignments.insert().values(...)).
3. React State — Don't store object snapshots: Store IDs, derive objects: const editingNode = editingNodeId ? findNode(editingNodeId, tree?.tree_structure) : null.
4. Modal Draft State — Exclude store-managed fields: const { children, ...draftWithoutChildren } = draft; updateNode(node.id, draftWithoutChildren).
5. Multiple FKs to same table: Specify foreign_keys on BOTH sides: author = relationship("User", foreign_keys=[author_id], back_populates="trees").
6. PostgreSQL NULL in UUID columns: SELECT 'tag', 'slug', NULL::uuid as team_id — must cast NULL to uuid.
7. API Path Gotcha: Frontend apiClient baseURL is http://localhost:8000/api/v1 — use relative paths WITHOUT /api/v1/ prefix. Invite codes endpoint is /invites (NOT /invite-codes).
8. CORS errors can mask 500s: Check backend logs first. Also run alembic upgrade head after pulling changes.
9. Public endpoints with optional auth: Use manual _get_optional_user(request, db) helper, NOT Optional[User] param.
10. React Router — Clear dirty state before navigation: markSaved() before navigate().
11. CORS expose_headers for custom response headers: Must be set in BOTH CORS branches in main.py.
12. TreeStructure vs Tree types: TreeStructure is for node structure only — it does NOT have tree_type, name, etc. Those are on Tree.
13. Login redirect state format: navigate('/login', { state: { from: { pathname: '/path' } } }) — expects state.from.pathname (object), NOT a plain string.
14. Type-aware routing: Always use getTreeNavigatePath(treeId, treeType) from @/lib/routing. Procedural flows use /flows/:id/navigate.
15. Session sharing types: Use tree_snapshot?.tree_type to determine flow type from session data.
16. tree_type has three values: 'troubleshooting' | 'procedural' | 'maintenance'. Use PROCEDURAL_TREE_TYPES set when checking for step-based flow types.
17. Alembic autogenerate can be destructive: Always review generated migrations. Prefer manual migrations for new tables.
18. Pydantic partial updates — use model_fields_set: Check data.model_fields_set to distinguish "field not sent" from "field sent as null".
19. gh pr merge fails with worktrees: Use the API: gh api repos/ORG/REPO/pulls/N/merge --method PUT --field merge_method=squash.
20. 'answer' node type in TreeStructure: Transient stub type used only in the canvas editor. Must explicitly handle in any node.type switch.
21. Test fixtures in conftest.py: client, test_db, test_user, auth_headers, test_tree, test_admin, admin_auth_headers. NO async_client or engineer_token.
22. Worktree venv path: Use backend/venv/bin/python -m pytest ... from the worktree directory.
23. Action nodes navigate via next_node_id, not children: Must set next_node_id on action nodes for AI generation or manual editing.
24. Anthropic model IDs: Both alias (claude-sonnet-4-6) and dated forms work. Default: claude-sonnet-4-6.
25. Claude API may wrap JSON responses in markdown fences: Always strip fences before parsing. See _strip_markdown_fences().
26. sessionsApi.list supports batch_id filter.
27. Maintenance batch sessions are created all-at-once at launch.
28. AI tests in CI need ai_enabled mock: Mock settings.ai_enabled = True via PropertyMock. See tests/test_ai_chat.py.
29. ESLint no-unused-vars has no argsIgnorePattern: Use // eslint-disable-next-line or remove the param.
30. Alembic env.py must import all models.
31. JSONB fields — convert datetime to .isoformat() string.
32. Export pipeline order matters: Generate → resolve variables → apply redaction. Redaction MUST run last.
33. Railway: DATABASE_URL_SYNC is a property, not an env var.
34. Railway: run migrations in Docker CMD, not releaseCommand.
35. bcrypt==4.0.1 pinned for passlib compatibility.
36. Email validator rejects .local TLD: Use example.com for test/seed emails.
37. First deployed user needs manual admin promotion via SQL.
38. Alembic migrations MUST use sequential numbered prefixes: Always pass --rev-id flag.
39. Platform settings for feature toggles: Use SettingsManager.get("key", db, default=True).
40. Survey public routes: Add at top level in router.tsx alongside /login.
Archived Lessons (41-70)
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. 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: 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: 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_* var must be added as ARG + ENV in frontend/Dockerfile. Railway env vars are runtime-only 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() — no intake form screen or "Start" button. Variables filled inline. Troubleshooting flows DO have a start screen.
62. Playwright strict mode — scope selectors: Step titles appear in both sidebar and main heading. Use getByRole('heading', { name }) for main content.
63. Node 20 required for frontend builds: export NVM_DIR="$HOME/.nvm" && source "$NVM_DIR/nvm.sh" && nvm use 20. Or: PATH="$HOME/.nvm/versions/node/v20.19.0/bin:$PATH".
64. PostHog product analytics: PostHogProvider in main.tsx. Event helpers in lib/analytics.ts. identifyUser() in authStore.fetchUser(), resetAnalytics() on logout. Env vars: VITE_PUBLIC_POSTHOG_KEY, VITE_PUBLIC_POSTHOG_HOST.
65. Local Docker Compose uses resolutionflow database on port 5433: Container resolutionflow_postgres, DB resolutionflow (not patherly), port 5433. Playwright config defaults must match.
66. Dev environment runs on Hostinger VPS (46.202.92.250): CORS must include VPS IP in CORS_ORIGINS and FRONTEND_URL. See DEV-ENV.md.
67. Tree editor route is /trees/new: NOT /editor/new. Use getTreeEditorPath() from @/lib/routing.
68. APScheduler jobs need max_instances=1: Without it, overlapping runs can process the same records twice (TOCTOU race).
69. PostgreSQL func.sum(case(...)) returns Decimal via asyncpg: Cast to int() before storing in Pydantic dict[str, Any] fields.
70. Toast library uses toast.warning() not toast.warn(): Import from @/lib/toast. Methods: success, error, warning, info.