Files
resolutionflow/docs/LESSONS-ARCHIVE.md
Michael Chihlas bea34229d6
Some checks failed
Mirror to GitHub / mirror (push) Successful in 4s
CI / backend (pull_request) Failing after 18m54s
CI / frontend (pull_request) Failing after 47s
CI / e2e (pull_request) Has been skipped
chore: bump version and changelog (v0.1.0.0)
Add CW security roles reference docs and PSA ticket management plan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 14:44:03 +00:00

9.5 KiB
Raw Permalink Blame History

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: titlequestiondescriptioncontentlabel. 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.