From 27624fbe552b43811cf03ddf4b963adc3b9e7b69 Mon Sep 17 00:00:00 2001 From: Michael Chihlas Date: Tue, 3 Feb 2026 21:17:54 -0500 Subject: [PATCH] fix: Custom step navigation bugs - go-back, descendants, redundant checkbox - Show previously-created custom steps as clickable options on decision nodes so they remain accessible after going back - Fix breadcrumb to show custom step titles instead of raw UUIDs - Fix ContinuationModal to show grandchildren (two levels deep) instead of immediate children that duplicate option labels - Remove redundant "Save to Library" checkbox from StepForm since PostStepActionModal now handles that decision Co-Authored-By: Claude Opus 4.5 --- .gitignore | 5 + LESSONS-LEARNED.md | 156 +++++++++++++++++- .../step-library/CustomStepModal.tsx | 4 +- .../src/components/step-library/StepForm.tsx | 20 +-- frontend/src/pages/TreeNavigationPage.tsx | 83 ++++++++-- 5 files changed, 234 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index 76f92d19..c7094fe6 100644 --- a/.gitignore +++ b/.gitignore @@ -205,3 +205,8 @@ cython_debug/ marimo/_static/ marimo/_lsp/ __marimo__/ + +# Railway CLI (local tooling) +node_modules/ +package.json +package-lock.json diff --git a/LESSONS-LEARNED.md b/LESSONS-LEARNED.md index 6ead785a..3d42395d 100644 --- a/LESSONS-LEARNED.md +++ b/LESSONS-LEARNED.md @@ -2,7 +2,7 @@ > **Purpose:** This file documents bugs, fixes, and gotchas encountered during development. > **For Claude Code:** Read this file at the start of each session to avoid repeating past mistakes. -> **Last Updated:** January 30, 2026 +> **Last Updated:** February 1, 2026 --- @@ -570,6 +570,160 @@ pip install httpx --- +## Railway Deployment + +### DATABASE_URL_SYNC Must Derive from DATABASE_URL ⚠️ CRITICAL +**Problem:** Alembic migrations run but connect to localhost instead of Railway's Postgres. Database tables never get created. + +**Cause:** `DATABASE_URL_SYNC` was a separate pydantic field with a default value pointing to `localhost:5432`. Railway only provides `DATABASE_URL`, so the sync version wasn't being set correctly. + +**Solution:** Make `DATABASE_URL_SYNC` a property that derives from `DATABASE_URL`: +```python +# BAD: Separate field with default +DATABASE_URL_SYNC: str = "postgresql://postgres:postgres@localhost:5432/patherly" + +# GOOD: Property that derives from DATABASE_URL +@property +def DATABASE_URL_SYNC(self) -> str: + """Get sync URL by removing asyncpg prefix from DATABASE_URL.""" + return self.DATABASE_URL.replace("postgresql+asyncpg://", "postgresql://", 1) +``` + +**Files affected:** `backend/app/core/config.py` + +--- + +### Railway releaseCommand May Not Execute +**Problem:** Migrations in `railway.toml` `releaseCommand` don't run. Deploy logs show the app starting but no migration output. + +**Cause:** Railway's `releaseCommand` feature may not work reliably with all configurations, especially with Dockerfile builds. + +**Solution:** Run migrations in the Docker CMD instead: +```dockerfile +# Instead of relying on railway.toml releaseCommand: +CMD alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-8000} +``` + +**Files affected:** `backend/Dockerfile`, `backend/railway.toml` (keep releaseCommand as backup) + +--- + +### Alembic env.py Must Import All Models +**Problem:** Migration runs but table isn't created. Or migration fails with model-related errors. + +**Cause:** Alembic's `env.py` only discovers models that are imported. If you add a new model (like `InviteCode`), you must add it to the imports. + +**Solution:** Add new models to the import statement in `alembic/env.py`: +```python +# BEFORE: Missing InviteCode +from app.models import User, Team, Tree, Session, Attachment + +# AFTER: All models imported +from app.models import User, Team, Tree, Session, Attachment, InviteCode +``` + +**Files affected:** `backend/alembic/env.py` + +--- + +### New Users Default to "engineer" Role, Not Admin +**Problem:** After registering, user cannot access admin-only endpoints (like creating invite codes). Returns 403 Forbidden. + +**Cause:** The User model defaults to `role="engineer"`. The first user isn't automatically made admin. + +**Solution:** Manually promote user to admin via Railway's Postgres Query tab: +```sql +UPDATE users SET role = 'admin' WHERE email = 'your@email.com'; +``` + +**Then log out and back in** to get a new JWT with the updated role. + +**Files affected:** Database (manual update required for first admin) + +--- + +### Frontend VITE_API_URL Must Use HTTPS, No Port +**Problem:** Frontend gets "Network Error" when trying to call API on Railway. + +**Cause:** Common mistakes: +- Using `http://` instead of `https://` +- Including port number (`:8080`) when Railway handles routing +- Typos in variable names (e.g., `FRONTED_URL` vs `FRONTEND_URL`) + +**Solution:** Ensure correct format: +``` +# WRONG +VITE_API_URL=http://api.patherly.com:8080 +VITE_API_URL=http://api.patherly.com + +# CORRECT +VITE_API_URL=https://api.patherly.com +``` + +**Note:** This is a **build-time** variable. After changing it, you must trigger a new frontend build/deploy. + +--- + +### Swagger OAuth2 Password Flow Returns 401 +**Problem:** Clicking "Authorize" in Swagger UI with username/password returns 401, even with correct credentials. + +**Workaround:** Use the login endpoint directly instead: +1. Call `POST /api/v1/auth/login` with your credentials +2. Copy the `access_token` from the response +3. Click "Authorize" button +4. Paste just the token (no "Bearer" prefix) in the authorization field + +**Files affected:** This is a Swagger UI / FastAPI OAuth2 configuration quirk + +--- + +### Seed Script: Use --email/--password Instead of Creating User +**Problem:** Seed script fails when `REQUIRE_INVITE_CODE=true` because it tries to register a new seed user without an invite code. + +**Solution:** Modified seed script to accept admin credentials via CLI arguments: +```bash +python -m scripts.seed_trees \ + --api-url https://api.patherly.com/api/v1 \ + --email admin@patherly.com \ + --password "your-password" +``` + +**Files affected:** `backend/scripts/seed_trees.py` + +--- + +### Seed Script: Use venv Python, Not System Python +**Problem:** Running `python -m scripts.seed_trees` fails with `ModuleNotFoundError: No module named 'httpx'` even after installing httpx. + +**Cause:** Installing with system Python (`pip install httpx`) but running with venv Python, or vice versa. + +**Solution:** Always use the venv's Python explicitly: +```powershell +# Use venv Python directly +./venv/Scripts/python -m scripts.seed_trees --api-url ... --email ... --password ... + +# Or ensure venv is activated first +.\venv\Scripts\Activate +python -m scripts.seed_trees ... +``` + +**Files affected:** Any script in `backend/scripts/` + +--- + +### Railway Environment Variable Naming +**Problem:** Environment variable isn't being read by the application. + +**Checklist:** +- Case-sensitive: `FRONTEND_URL` ≠ `frontend_url` ≠ `Frontend_Url` +- No typos: `FRONTEND_URL` ≠ `FRONTED_URL` +- Empty string vs not set: Setting a variable to empty string is different from not setting it +- Delete stale variables: If you changed a variable from a field to a property (like `DATABASE_URL_SYNC`), delete the Railway variable + +**Files affected:** Railway dashboard → Service → Variables tab + +--- + ## Adding New Lessons When you encounter and fix a bug, add it here with: diff --git a/frontend/src/components/step-library/CustomStepModal.tsx b/frontend/src/components/step-library/CustomStepModal.tsx index c0286890..cd221204 100644 --- a/frontend/src/components/step-library/CustomStepModal.tsx +++ b/frontend/src/components/step-library/CustomStepModal.tsx @@ -36,9 +36,7 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod if (!isOpen) return null - const handleFormSubmit = async (data: StepCreate, _saveToLibrary: boolean) => { - // Note: saveToLibrary preference is no longer used here - the PostStepActionModal - // handles the decision of whether to save to library, use now, or both + const handleFormSubmit = async (data: StepCreate) => { setIsSubmitting(true) setError(null) diff --git a/frontend/src/components/step-library/StepForm.tsx b/frontend/src/components/step-library/StepForm.tsx index fbb78d1c..4c2254b4 100644 --- a/frontend/src/components/step-library/StepForm.tsx +++ b/frontend/src/components/step-library/StepForm.tsx @@ -5,7 +5,7 @@ import { stepCategoriesApi } from '@/api' import type { StepCreate, StepCategory, StepCommand } from '@/types/step' interface StepFormProps { - onSubmit: (data: StepCreate, saveToLibrary: boolean) => void + onSubmit: (data: StepCreate) => void onCancel: () => void initialData?: Partial } @@ -31,8 +31,6 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) { const [visibility, setVisibility] = useState<'private' | 'team' | 'public'>( initialData?.visibility || 'private' ) - const [saveToLibrary, setSaveToLibrary] = useState(true) - // Categories const [categories, setCategories] = useState([]) @@ -131,7 +129,7 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) { tags: tags.length > 0 ? tags : undefined } - onSubmit(data, saveToLibrary) + onSubmit(data) } return ( @@ -367,20 +365,6 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) { - {/* Save to Library Checkbox */} -
- setSaveToLibrary(e.target.checked)} - className="h-4 w-4 rounded border-input" - /> - -
- {/* Actions */}
))}
+ {/* Previously-created custom steps at this node */} + {customSteps.filter(cs => cs.inserted_after_node_id === currentNodeId).length > 0 && ( +
+

+ Your Custom Steps +

+ {customSteps + .filter(cs => cs.inserted_after_node_id === currentNodeId) + .map(cs => ( + + ))} +
+ )} + {/* Add Custom Step Button */}