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

148 lines
9.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`.