docs: archive lessons 1-40, condense CLAUDE.md from 40KB to 26KB
Move early lessons (fixes baked into codebase) to docs/LESSONS-ARCHIVE.md. Condense lessons 41-65 to one-liners. Reduces system prompt token usage by ~34% while preserving all actively relevant context. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
163
CLAUDE.md
163
CLAUDE.md
@@ -1,6 +1,6 @@
|
||||
# CLAUDE.md - Patherly / ResolutionFlow Project Context
|
||||
|
||||
> **Last Updated:** March 11, 2026
|
||||
> **Last Updated:** March 16, 2026
|
||||
|
||||
---
|
||||
|
||||
@@ -257,166 +257,47 @@ gh run view <id> --json jobs --jq '.jobs[] | {name: .name, conclusion: .conclusi
|
||||
|
||||
## Critical Lessons Learned
|
||||
|
||||
### Top Gotchas (most commonly hit)
|
||||
> Lessons 1-40 archived to `docs/LESSONS-ARCHIVE.md` — fixes are baked into the codebase. Consult if you hit a regression.
|
||||
|
||||
**1. DateTime Handling — Always timezone-aware:**
|
||||
### Active Lessons (41+)
|
||||
|
||||
```python
|
||||
# CORRECT
|
||||
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
|
||||
# NEVER use datetime.utcnow()
|
||||
```
|
||||
**41. Assistant chat uses local React state, not Zustand:** `AssistantChatPage.tsx` uses `useState` for `chats`, `messages`, `input`, `loading`. No store.
|
||||
|
||||
**2. SQLAlchemy Async — No lazy loading on new objects:**
|
||||
**42. Public pages use raw `fetch()`, not `apiClient`:** Survey, shared sessions, and no-auth pages use `fetch()` with full URL. `apiClient` requires auth tokens.
|
||||
|
||||
```python
|
||||
# WRONG — MissingGreenlet error
|
||||
new_tree = Tree(...); db.add(new_tree); await db.flush()
|
||||
new_tree.tags.append(tag) # Lazy load fails!
|
||||
**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).
|
||||
|
||||
# CORRECT — Use direct SQL for junction tables
|
||||
await db.execute(tree_tag_assignments.insert().values(tree_id=new_tree.id, tag_id=tag.id))
|
||||
```
|
||||
**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]`.
|
||||
|
||||
**3. React State — Don't store object snapshots:**
|
||||
**45. Intake form field schema:** Uses `variable_name` and `field_type` (NOT `name` and `type`).
|
||||
|
||||
```tsx
|
||||
// WRONG — snapshot won't update
|
||||
const [editingNode, setEditingNode] = useState(node)
|
||||
// CORRECT — store ID, derive object
|
||||
const [editingNodeId, setEditingNodeId] = useState(node.id)
|
||||
const editingNode = editingNodeId ? findNode(editingNodeId, tree?.tree_structure) : null
|
||||
```
|
||||
**46. `CreateFlowDropdown` uses `AIPromptDialog`:** Opens prompt modal, starts AI session, generates flow, navigates to editor with `{ state: { aiPanelOpen: true, sessionId } }`.
|
||||
|
||||
**4. Modal Draft State — Exclude store-managed fields:**
|
||||
**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.
|
||||
|
||||
```tsx
|
||||
const { children, ...draftWithoutChildren } = draft
|
||||
updateNode(node.id, draftWithoutChildren) // Don't overwrite children
|
||||
```
|
||||
**48. Tree orphan validation uses dynamic root ID:** Orphan check compares against `state.treeStructure?.id` (NOT hardcoded `'root'`).
|
||||
|
||||
**5. Multiple FKs to same table — Specify `foreign_keys` on BOTH sides:**
|
||||
**49. Full-stack features — verify both ends:** Check the full data flow: schema → endpoint → API client → hook → store → UI.
|
||||
|
||||
```python
|
||||
author = relationship("User", foreign_keys=[author_id], back_populates="trees")
|
||||
```
|
||||
**50. Anthropic SDK retry:** Set `max_retries=1` to fail fast. Default `max_retries=2` can take 3× timeout.
|
||||
|
||||
**6. PostgreSQL NULL in UUID columns:**
|
||||
**51. AI model tier routing:** Use `settings.get_model_for_action(action_type)`. Model IDs: use alias form (`claude-sonnet-4-6`).
|
||||
|
||||
```sql
|
||||
SELECT 'tag', 'slug', NULL::uuid as team_id -- Must cast NULL to uuid
|
||||
```
|
||||
**52. Mobile scroll-to-top:** Use `ref.current.scrollIntoView()`, not `window.scrollTo()`. Trigger via `useEffect`.
|
||||
|
||||
**7. API Path Gotcha:** Frontend `apiClient` baseURL is `http://localhost:8000/api/v1` — use relative paths WITHOUT `/api/v1/` prefix (e.g., `/admin/users` not `/api/v1/admin/users`). Invite codes endpoint is `/invites` (NOT `/invite-codes`).
|
||||
**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.
|
||||
|
||||
**8. CORS errors can mask 500s:** Check backend logs first. Also run `alembic upgrade head` after pulling changes.
|
||||
**54. React Flow CSS in Tailwind v4:** Import in `index.css`, not component JS. Override dark theme using `--xy-*` CSS custom properties.
|
||||
|
||||
**11. CORS `expose_headers` for custom response headers:** Browsers block frontend from reading custom headers (e.g. `X-Redaction-Summary`) unless CORS middleware includes `expose_headers=["X-Custom-Header"]`. Must be set in BOTH CORS branches in `main.py`.
|
||||
**55. App shell height chain:** Every wrapper between `.main-content` and canvas needs `flex` + `flex-1` + `min-h-0` or `h-full`.
|
||||
|
||||
**9. Public endpoints with optional auth:** Use manual `_get_optional_user(request, db)` helper, NOT `Optional[User]` param (FastAPI treats it as Pydantic field).
|
||||
**56. Railway backend service name is `patherly`:** Production DB name is `railway`. Public Postgres proxy: `interchange.proxy.rlwy.net:45797`.
|
||||
|
||||
**10. React Router — Clear dirty state before navigation:**
|
||||
**57. Node field priority:** `title` → `question` → `description` → `content` → `label`. See `copilot_service.py`.
|
||||
|
||||
```tsx
|
||||
markSaved() // Clear isDirty BEFORE navigate()
|
||||
navigate(`/trees/${newTree.id}/edit`)
|
||||
```
|
||||
**58. `scriptGeneratorStore.generate()` optional param:** Always wrap: `onClick={() => generate()}`, never `onClick={generate}`.
|
||||
|
||||
**12. TreeStructure vs Tree types:** `TreeStructure` is for node structure only — it does NOT have `tree_type`, `name`, etc. Those are on `Tree`. JSONB tree snapshots need `TreeStructure & Record<string, unknown>` for extra fields.
|
||||
|
||||
**13. Login redirect state format:** `navigate('/login', { state: { from: { pathname: '/path' } } })` — LoginPage expects `state.from.pathname` (object), NOT a plain string.
|
||||
|
||||
**14. Type-aware routing for trees/sessions:** Always use `getTreeNavigatePath(treeId, treeType)` from `@/lib/routing` instead of hardcoding `/trees/:id/navigate`. Procedural flows use `/flows/:id/navigate`. Session resume passes `{ state: { sessionId } }`. TreeNavigationPage has a safety redirect for procedural trees.
|
||||
|
||||
**15. Session sharing types:** `TreeSnapshot` extends `TreeStructure` but session snapshots from the API include extra fields like `tree_type`. Use `tree_snapshot?.tree_type` to determine flow type from session data.
|
||||
|
||||
**16. tree_type has three values:** `'troubleshooting' | 'procedural' | 'maintenance'`. Maintenance reuses the procedural execution engine. Use `PROCEDURAL_TREE_TYPES` set from `core/tree_validation.py` when checking for step-based flow types.
|
||||
|
||||
**17. Alembic autogenerate can be destructive:** `alembic revision --autogenerate` may generate DROP TABLE/ALTER COLUMN ops for unrelated tables (especially junction tables and tables with complex FKs). Always review generated migrations carefully. Prefer manual migrations for new tables: `alembic revision -m "desc"` then write the `upgrade()`/`downgrade()` by hand.
|
||||
|
||||
**18. Pydantic partial updates — use `model_fields_set`:** When a PUT/PATCH endpoint needs to distinguish "field not sent" from "field sent as null", check `data.model_fields_set` instead of `data.field is not None`. This allows clients to explicitly clear nullable fields like `description`.
|
||||
|
||||
**19. `gh pr merge` fails with worktrees:** When `main` is checked out in the primary worktree, `gh pr merge` crashes with "fatal: 'main' is already used by worktree". Use the API directly instead: `gh api repos/ORG/REPO/pulls/N/merge --method PUT --field merge_method=squash`
|
||||
|
||||
**20. `'answer'` node type in TreeStructure:** `answer` is a transient stub type used only in the canvas editor. Any code that switches on `node.type` (validation, markdown serializer, session nav guard) must explicitly handle `'answer'` or it will hit an unhandled-type error.
|
||||
|
||||
**21. Test fixtures in `conftest.py`:** Available fixtures are `client` (async HTTP client), `test_db` (async session), `test_user` (registers user, returns email/password/user_data), `auth_headers` (Bearer token dict), `test_tree` (creates a tree), `test_admin` (super_admin user), `admin_auth_headers` (admin Bearer token). There is NO `async_client` or `engineer_token` fixture.
|
||||
|
||||
**22. Worktree venv path:** Git worktrees (`.worktrees/*/`) do NOT have their own Python venv. Use the main repo's venv: `backend/venv/bin/python -m pytest ...` from the worktree directory.
|
||||
|
||||
**23. Action nodes navigate via `next_node_id`, not `children`:** `TreeNavigationPage.tsx` handles action nodes by following `next_node_id` only — the `children` array on action nodes is ignored at runtime. Action nodes without `next_node_id` render no "Continue" button (dead end). Any AI generation or manual tree editing must set `next_node_id` on action nodes.
|
||||
|
||||
**24. Anthropic model IDs:** Both alias (`claude-sonnet-4-6`) and dated (`claude-haiku-4-5-20251001`) forms work. Current default: `claude-sonnet-4-6`. See `backend/app/core/config.py` → `AI_MODEL_ANTHROPIC`.
|
||||
|
||||
**25. Claude API may wrap JSON responses in markdown fences:** When parsing AI-generated JSON, always strip ` ```json ... ``` ` fences before parsing. See `_strip_markdown_fences()` in `ai_tree_generator_service.py`.
|
||||
|
||||
**26. `sessionsApi.list` supports `batch_id` filter (added Feb 2026):** Both backend `GET /sessions` and frontend `SessionListParams` accept `batch_id` for querying all sessions in a maintenance batch. Use `sessionsApi.list({ batch_id })` to fetch batch-scoped sessions.
|
||||
|
||||
**27. Maintenance batch sessions are created all-at-once at launch:** All sessions in a batch exist immediately after `batchLaunchApi.launch()` with `batch_id` + `target_label` set. `started_at` is null until a user begins executing that target — there is no "pending session creation" state.
|
||||
|
||||
**28. AI tests in CI need `ai_enabled` mock:** Backend tests that hit AI endpoints must mock `settings.ai_enabled = True` via `PropertyMock` since CI has no `ANTHROPIC_API_KEY`. See `tests/test_ai_chat.py` `_enable_ai` fixture pattern.
|
||||
|
||||
**29. ESLint `no-unused-vars` has no `argsIgnorePattern`:** Underscore-prefixed callback params (e.g., `_count`) still trigger errors. Use `// eslint-disable-next-line @typescript-eslint/no-unused-vars` or remove the param if the type signature allows it.
|
||||
|
||||
**30. Alembic `env.py` must import all models:** New models won't be discovered by `--autogenerate` unless imported in `alembic/env.py`. If a migration runs but the table isn't created, check imports first.
|
||||
|
||||
**31. JSONB fields — convert datetime to `.isoformat()` string:** Storing `datetime` objects directly in JSONB columns causes serialization errors. Always convert: `"timestamp": datetime.now(timezone.utc).isoformat()`.
|
||||
|
||||
**32. Export pipeline order matters:** Generate export → resolve session variables → apply redaction. Redaction MUST run last or sensitive data injected via variables bypasses it. See `sessions.py` export endpoint.
|
||||
|
||||
**33. Railway: `DATABASE_URL_SYNC` is a property, not an env var:** It derives from `DATABASE_URL` by replacing `postgresql+asyncpg://` with `postgresql://`. Delete any stale `DATABASE_URL_SYNC` variable in Railway dashboard.
|
||||
|
||||
**34. Railway: run migrations in Docker CMD, not `releaseCommand`:** `releaseCommand` in `railway.toml` is unreliable. Use `CMD alembic upgrade head && uvicorn ...` in the Dockerfile instead.
|
||||
|
||||
**35. `bcrypt==4.0.1` pinned for passlib compatibility:** Newer bcrypt versions break password hashing. Keep `bcrypt==4.0.1` and `passlib[bcrypt]==1.7.4` pinned in `requirements.txt`.
|
||||
|
||||
**36. Email validator rejects `.local` TLD:** Pydantic's email validation (via `email-validator`) rejects `.local` as a reserved TLD. Use `example.com` for test/seed user emails.
|
||||
|
||||
**37. First deployed user needs manual admin promotion:** New users default to `engineer` role. Promote via SQL: `UPDATE users SET role = 'admin' WHERE email = '...';` then re-login for new JWT.
|
||||
|
||||
**39. Platform settings for feature toggles:** Use `SettingsManager.get("key", db, default=True)` to gate features. Add toggle in `admin/SettingsPage.tsx` (same pattern as `maintenance_mode`). Frontend can check status via a lightweight GET endpoint without auth.
|
||||
|
||||
**40. Survey public routes:** `SurveyPage` is a public route (no auth). Survey invite tokens are passed as `?token=` query param. Add public pages at top level in `router.tsx` alongside `/login`.
|
||||
|
||||
**38. Alembic migrations MUST use sequential numbered prefixes:** Check `backend/alembic/versions/` for the highest numbered migration and use the next number. Format: `XXX_descriptive_name.py` (e.g., `040_add_whatever.py`). NEVER use auto-generated revision IDs like `0f1ca2af3647`. Always pass `--rev-id` flag: `alembic revision --autogenerate -m "desc" --rev-id=040`.
|
||||
|
||||
**41. Assistant chat uses local React state, not a Zustand store:** `AssistantChatPage.tsx` manages `chats`, `activeChatId`, `messages`, `input`, `loading` as `useState`. Don't look for a store when modifying assistant chat.
|
||||
|
||||
**42. Public pages use raw `fetch()`, not `apiClient`:** Survey, shared sessions, and other no-auth pages call the API directly via `fetch()` with `${import.meta.env.VITE_API_URL || 'http://localhost:8000'}/api/v1/...`. Don't use `apiClient` — it requires auth tokens and uses relative paths.
|
||||
|
||||
**43. Adding new email types:** Add static async method to `EmailService` in `core/email.py`. Pattern: check `settings.email_enabled`, import `resend`, build HTML string, call `resend.Emails.send()`, return `bool`. Always fire-and-forget from endpoints (log errors, don't fail the request).
|
||||
|
||||
**44. AI Chat Builder (Flow Assist) is flow-type-aware:** `ai_chat_service.py` dispatches system prompts, response markers, and validation by `flow_type`. Troubleshooting uses `[TREE_UPDATE]` markers + `validate_generated_tree()`. Procedural/maintenance uses `[STEPS_UPDATE]` markers + `validate_generated_procedural_steps()`. Both support `[METADATA]`; procedural also supports `[INTAKE_FORM]`.
|
||||
|
||||
**45. Intake form field schema uses `variable_name` and `field_type`:** NOT `name` and `type`. Pattern: `{"variable_name": "server_name", "label": "Server Name", "field_type": "text", "required": true, "display_order": 1}`. Used in `tree_validation.py` and AI prompt examples.
|
||||
|
||||
**46. `CreateFlowDropdown` uses `AIPromptDialog` for AI-assisted creation:** Opens a simple prompt dialog modal (not a separate page). The dialog starts an AI session, generates the flow, imports it, and navigates to the editor with `{ state: { aiPanelOpen: true, sessionId } }`. The old standalone `/ai/chat` page and `AIFlowBuilderModal` have been removed.
|
||||
|
||||
**47. Editor-Embedded Flow Assist architecture:** AI assistance is embedded in each editor via `EditorAIPanel` (320px side panel) + `useEditorAI` hook + `ContextMenu`. Tree editor: panel replaces node editor panel (single-panel rule). Procedural editor: panel sits alongside step list (flex layout). Ghost nodes/steps use `_suggestion: true` flag with dashed borders + accept/dismiss controls. Action types (`generate_branch`, `modify_node`, `add_steps`, etc.) route to model tiers via `settings.get_model_for_action()`. Delta responses use `[DELTA]...[/DELTA]` markers. Suggestion audit trail in `ai_suggestions` table.
|
||||
|
||||
**48. Tree orphan validation uses dynamic root ID:** `treeEditorStore.ts` orphan check compares against `state.treeStructure?.id` (NOT hardcoded `'root'`). AI-generated trees use descriptive root IDs like `"verify-account-exists"`.
|
||||
|
||||
**49. Full-stack features — verify both ends:** When adding a field to a backend API response (e.g., `working_tree` in `AIChatMessageResponse`), always verify the frontend consumer actually reads and uses it. Similarly, when a frontend hook/component expects data from an API, confirm the backend populates it. Check the full data flow: schema → endpoint → API client → hook → store → UI.
|
||||
|
||||
**50. Anthropic SDK retry behavior:** Default `max_retries=2` with exponential backoff can cause requests to take 3× the timeout (e.g., 45s × 3 = 135s). Set `max_retries=1` in `AnthropicProvider` to fail fast. Current timeout is `AI_REQUEST_TIMEOUT_SECONDS=120`.
|
||||
|
||||
**51. AI model tier routing:** `config.py` has `AI_MODEL_TIERS` (fast/standard) and `ACTION_MODEL_MAP` mapping action types to tiers. Use `settings.get_model_for_action(action_type)` to resolve concrete model names. Model IDs must be valid — use alias form (`claude-sonnet-4-6`) not invented dated forms.
|
||||
|
||||
**52. Mobile scroll-to-top — use `scrollIntoView`, not `window.scrollTo`:** Mobile browsers (iOS Safari, Firefox Android) often ignore `window.scrollTo()`. Use a ref at the top of the page and call `ref.current.scrollIntoView({ behavior: 'smooth', block: 'start' })` instead. Trigger via `useEffect` on the state change (not inline with `setState`) so the DOM has committed before scrolling.
|
||||
|
||||
**53. Flex height chain — every ancestor must be a flex container for `flex-1` to work:** If a child uses `flex-1` to fill its parent, the parent MUST have `display: flex` (the `flex` class). A missing `flex` on any wrapper div breaks the entire height chain, causing React Flow (and any `h-full` descendant) to collapse to 0 height. Debug with: `let el = document.querySelector('.react-flow'); while(el) { console.log(el.getBoundingClientRect().height, el.className); el = el.parentElement; }`. The break is where height drops to 0. Common symptom: React Flow error `"parent container needs a width and a height"`.
|
||||
|
||||
**54. React Flow CSS in Tailwind v4 — import in `index.css`, not component JS:** With `@tailwindcss/vite`, importing `@xyflow/react/dist/style.css` inside a component file causes the plugin to process/wrap it in a cascade layer, lowering specificity. Import it in `index.css` instead: `@import '@xyflow/react/dist/style.css';` after `@import 'tailwindcss';`. Override dark theme using `--xy-*` CSS custom properties (e.g., `--xy-edge-stroke-default`) on `.react-flow.dark`, NOT old-style direct property selectors like `.react-flow__edge-path { stroke: ... }`.
|
||||
|
||||
**55. App shell height chain for full-height pages (tree editor, procedural editor):** The CSS Grid app shell (`app-shell`) → `.main-content` → page component chain must preserve height. `.main-content` is a grid cell with implicit height from `1fr`. Pages using React Flow or other full-height layouts need every wrapper div between `.main-content` and the canvas to either use `flex` + `flex-1` + `min-h-0` or explicit `h-full`. Adding ANY wrapper div (e.g., for animations, transitions) without proper height classes will collapse the canvas to 0.
|
||||
|
||||
**56. Railway backend service name is `patherly`:** Use `railway variables --service patherly --json` to get env vars. Production DB name is `railway` (not `patherly` or `resolutionflow`). Public Postgres proxy: `interchange.proxy.rlwy.net:45797`. Internal URL only reachable via `railway run`.
|
||||
|
||||
**57. Node field priority for display/context:** Nodes use different label fields by type — procedural steps use `title`+`description`, decision nodes use `question`, action/solution nodes use `title`. When reading a node's label generically, check: `title` → `question` → `description` → `content` → `label`. See `copilot_service.py` `_build_flow_context()`.
|
||||
|
||||
**58. `scriptGeneratorStore.generate()` has an optional `sessionId` param:** `generate(sessionId?: string)` — do NOT pass it as a bare `onClick={generate}` handler (TypeScript error: MouseEvent not assignable to string). Always wrap: `onClick={() => generate()}`.
|
||||
|
||||
**59. ConnectWise `clientId` is server-side config, not per-connection:** `clientId` is set in backend `config.py` as `CW_CLIENT_ID` — it identifies the ResolutionFlow integration app, not the MSP tenant. Per-connection credentials are only `company_id`, `public_key`, `private_key`, and `server_url`.
|
||||
**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.
|
||||
|
||||
|
||||
83
docs/LESSONS-ARCHIVE.md
Normal file
83
docs/LESSONS-ARCHIVE.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Lessons Archive (1-40)
|
||||
|
||||
> 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`.
|
||||
Reference in New Issue
Block a user