Development — Workflows, Debugging & Tools
1. Quick start for developers
bash# Clone
git clone https://github.com/haal-lab/Lawyer-Assistant.git
cd Lawyer-Assistant
# Install Node dependencies (root + frontend + electron)
npm install
cd frontend && npm install
cd electron && npm install
cd ../..
# Python env is auto-created by Electron on first run
# Or create manually:
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS/Linux
pip install -r backend/requirements.txt
# Run the app (starts backend + Vite + Electron)
npm run dev
What npm run dev does:
- Starts Electron main process
- Electron spawns Python backend (port 8765)
- Electron spawns Vite dev server (port 5173)
- Electron loads Vite URL in BrowserWindow
2. Development modes
Mode 1: Full app (Electron)
bashnpm run dev # or: npm start
Use when:
- Testing Electron-specific features (IPC, native dialogs)
- End-to-end testing
- Building/packaging
Logs:
- Backend logs → Electron console (stdout/stderr piped)
- Frontend logs → Electron DevTools (Cmd+Option+I / Ctrl+Shift+I)
Mode 2: Web-only (browser)
bashnpm run dev:web
Starts backend + frontend in parallel (no Electron).
Use when:
- Frontend-only work (faster iteration)
- Backend API testing (via browser DevTools Network tab)
- CSS/UI tweaks
Access: http://localhost:5173
Limitations:
- No native file dialogs (falls back to
<input webkitdirectory>) - No Electron IPC (workspace picker limited)
Mode 3: Backend-only
bashnpm run dev:backend
Starts only the FastAPI server (port 8765).
Use when:
- Backend-only work
- API testing with
curl/Postman - Running benchmarks
Test:
bashcurl http://localhost:8765/api/health
Mode 4: Frontend-only (Vite)
bashnpm run dev:frontend
Starts only the Vite dev server (requires backend running separately).
Use when:
- UI iteration (fastest hot reload)
- Component development
3. Hot reload behavior
Frontend (Vite + React Fast Refresh)
Hot Module Replacement (HMR):
- Edit a
.tsxfile → component re-renders without page reload - State is preserved (Zustand store not reset)
- Sub-second latency
Full reload triggers:
index.htmlchangesvite.config.tschanges- New dependencies added
Test HMR:
tsx// frontend/src/App.tsx
export function App() {
return (
<div>
<h1>Hello World</h1> {/* ← edit this */}
</div>
);
}
Save → page updates instantly without reload.
Backend (manual restart)
Python backend does not auto-reload — restart manually:
Windows:
cmdtaskkill /F /IM python.exe
npm run dev
macOS/Linux:
bashpkill -f "python.*main.py"
npm run dev
Why no auto-reload:
- Model loading is expensive (5-10s warmup)
- ChromaDB connections need clean shutdown
- Electron manages backend lifecycle
Workaround for rapid backend iteration:
bash# Terminal 1: backend with auto-reload (uvicorn --reload)
cd backend
.venv\Scripts\activate
uvicorn main:app --host 127.0.0.1 --port 8765 --reload
# Terminal 2: frontend
cd frontend
npm run dev
# Terminal 3: Electron (optional)
cd frontend/electron
npm run dev
Now backend auto-reloads on Python file changes.
4. Debugging strategies
Debugging frontend (React)
React DevTools:
- Open Electron app
- Press
Cmd+Option+I(Mac) orCtrl+Shift+I(Windows) - Install React DevTools extension (if in browser)
Zustand state inspection:
typescript// frontend/src/store/chatStore.ts
import { devtools } from 'zustand/middleware';
export const useChatStore = create<ChatState>()(
devtools(
persist(
(set) => ({ ... }),
{ name: 'freebuff-chat-storage' }
),
{ name: 'ChatStore' } // ← shows in Redux DevTools
)
);
Install Redux DevTools extension → see Zustand state live.
Console logging:
typescript// frontend/src/services/api.ts
export async function* streamChat(request: ChatRequest) {
console.log('[API] Streaming chat:', request);
for await (const event of parseSSEStream(response.body!)) {
console.log('[SSE]', event);
yield event;
}
}
Network inspection:
- DevTools → Network tab
- Filter by
localhost:8765 - Inspect SSE streams (EventStream)
Debugging backend (Python)
Print debugging:
python# backend/legal_retrieval/retrieval.py
def query(self, query: str, top_k: int = 5):
print(f"[DEBUG] Query: {query}, top_k: {top_k}")
results = self._search(query)
print(f"[DEBUG] Retrieved {len(results)} results")
return results
Structured logging:
pythonfrom legal_retrieval.logging_setup import get_logger
log = get_logger(__name__)
log.debug(f"Query: {query}, top_k: {top_k}")
log.info(f"Retrieved {len(results)} results")
Set log level:
bashexport PLR_LOG_LEVEL=DEBUG
python backend/main.py
Python debugger (pdb):
pythonimport pdb
def query(self, query: str, top_k: int = 5):
pdb.set_trace() # ← breakpoint here
results = self._search(query)
return results
Run backend manually (not via Electron):
bashcd backend
.venv\Scripts\activate
python main.py
When breakpoint hits:
(Pdb) p query
'What is rescission?'
(Pdb) p top_k
5
(Pdb) n # next line
(Pdb) c # continue
VS Code debugging:
.vscode/launch.json:
json{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Backend",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/backend/main.py",
"console": "integratedTerminal",
"env": {
"PLR_LOG_LEVEL": "DEBUG"
}
}
]
}
Set breakpoints in VS Code → F5 to start debugging.
Debugging Electron
Main process debugging:
frontend/electron/src/main/index.ts:
typescriptconsole.log('[Main] Starting backend on port 8765');
Logs appear in the terminal that ran npm run dev.
Renderer process debugging:
Same as frontend debugging (DevTools).
IPC debugging:
typescript// Main process
ipcMain.handle('folder:select', async () => {
console.log('[IPC] Folder select requested');
const result = await dialog.showOpenDialog({ properties: ['openDirectory'] });
console.log('[IPC] Selected:', result.filePaths);
return result.filePaths[0];
});
// Renderer (preload)
window.electron.invoke('folder:select').then(path => {
console.log('[Renderer] Received path:', path);
});
5. Database inspection
ChromaDB (vector store)
List collections:
pythonfrom chromadb import PersistentClient
client = PersistentClient(path="data/chroma_db")
print(client.list_collections())
# → [Collection(name="legal_chunks")]
Collection stats:
pythoncollection = client.get_collection("legal_chunks")
print(f"Count: {collection.count()}")
print(f"Metadata: {collection.metadata}")
Query vectors:
pythonresults = collection.query(
query_texts=["contract law"],
n_results=5,
)
for doc, dist in zip(results['documents'][0], results['distances'][0]):
print(f"{dist:.3f}: {doc[:100]}...")
Peek at stored chunks:
pythonsample = collection.peek(limit=10)
for meta in sample['metadatas']:
print(f"{meta['file_name']} p.{meta['page_number']}")
SQLite (conversations, flags)
LangGraph conversations:
bashsqlite3 data/conversations.db
sql.tables
-- → checkpoints, checkpoint_blobs, checkpoint_writes
SELECT thread_id, checkpoint_id, created_at
FROM checkpoints
ORDER BY created_at DESC
LIMIT 10;
Compliance flags:
bashsqlite3 data/flags.db
sql.tables
-- → flags
SELECT flag_id, document_id, severity, rule_description
FROM flags
WHERE status = 'open'
ORDER BY severity DESC;
Workspace history (per-project)
File: <workspace>/workspace/history.json
bashcat /path/to/project/workspace/history.json | jq .
Structure:
json{
"conversations": [ ... ],
"activeConversationId": "...",
"gpu_profile": { ... },
"working_set": [ ... ]
}
6. Performance profiling
Backend profiling
cProfile (Python built-in):
bashcd backend
python -m cProfile -o profile.out -m legal_retrieval.retrieval
Analyze:
pythonimport pstats
stats = pstats.Stats('profile.out')
stats.sort_stats('cumulative')
stats.print_stats(20) # top 20 functions
Line profiler:
bashpip install line_profiler
python# backend/legal_retrieval/retrieval.py
@profile # ← add decorator
def query(self, query: str):
# ... function body
Run:
bashkernprof -l -v backend/legal_retrieval/retrieval.py
GPU profiling
PyTorch profiler:
pythonimport torch.profiler
with torch.profiler.profile(
activities=[torch.profiler.ProfilerActivity.CPU, torch.profiler.ProfilerActivity.CUDA],
record_shapes=True,
) as prof:
# ... code to profile
embeddings = embedder.embed(texts)
print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=10))
nvidia-smi monitoring:
bashwatch -n 1 nvidia-smi
Frontend profiling
React DevTools Profiler:
- Open DevTools → Profiler tab
- Click ⏺️ Record
- Interact with app
- Stop recording
- Inspect flame graph (which components re-rendered)
Chrome DevTools Performance:
- DevTools → Performance tab
- Record → interact → stop
- See JavaScript execution, rendering, painting
7. Log file locations
| Log | Location | Purpose |
|---|---|---|
| Backend | logs/legal_retrieval.log | Rotating file (10MB, 5 backups) |
| Launcher | launcher/logs/app-*.log | App launch logs |
| Ollama serve | launcher/logs/ollama-serve.log | Ollama daemon logs |
| Electron console | Terminal stdout/stderr | Backend stdout piped to Electron |
Tail logs:
bashtail -f logs/legal_retrieval.log
Search logs:
bashgrep "ERROR" logs/legal_retrieval.log
grep "query" logs/legal_retrieval.log -i
8. CLI scripts for development
Located in backend/scripts/:
ingest_all.py — Bulk ingestion
bashcd backend
.venv\Scripts\activate
python scripts/ingest_all.py
# Options
python scripts/ingest_all.py --reset # wipe ChromaDB first
python scripts/ingest_all.py --device cpu # force CPU
python scripts/ingest_all.py --batch-size 16 # lower batch size
query.py — CLI search
bashcd backend
python scripts/query.py "What is rescission?"
# Options
python scripts/query.py "contract law" --top-k 10
python scripts/query.py "payment terms" --no-rerank
chat_cli.py — CLI chat
bashcd backend
python scripts/chat_cli.py
> What is rescission under contract law?
[Agent] Searching documents...
[Agent] Answer: Rescission is...
> exit
build_bm25_index.py — Rebuild BM25
bashcd backend
python scripts/build_bm25_index.py
Rebuilds data/bm25_index/bm25_index.pkl from ChromaDB chunks.
compare_v1_v2_retrieval.py — A/B test
bashcd backend
python scripts/compare_v1_v2_retrieval.py
Compares two retrieval configs side-by-side.
9. Testing during development
Run tests on file change (watch mode)
bashcd backend
pip install pytest-watch
ptw tests/ -v
Run specific tests
bash# Single file
pytest tests/test_phase3_chunking.py -v
# Single test
pytest tests/test_phase3_chunking.py::test_chunking_respects_sections -v
# Pattern match
pytest tests/ -k "chunking" -v
Frontend type checking
bashcd frontend
npm run typecheck
# Watch mode
npm run typecheck -- --watch
Lint
bash# Frontend
cd frontend
npm run lint
# Backend (if ruff/black/pylint installed)
cd backend
ruff check .
black --check .
10. Common development tasks
Add a new API endpoint
- Backend:
backend/main.py
python@app.get("/api/my-feature")
async def my_feature(param: str):
result = do_something(param)
return {"result": result}
- Frontend:
frontend/src/services/api.ts
typescriptexport async function myFeature(param: string): Promise<{ result: string }> {
const response = await fetch(`${API_BASE_URL}/api/my-feature?param=${param}`);
return response.json();
}
- UI:
frontend/src/App.tsx
typescriptconst handleClick = async () => {
const result = await api.myFeature("test");
console.log(result);
};
Add a new environment variable
- Backend:
backend/legal_retrieval/config.py
python@dataclass(frozen=True)
class MyConfig:
my_setting: int = int(os.environ.get("PLR_MY_SETTING", "42"))
- Usage:
bashexport PLR_MY_SETTING=100
python backend/main.py
Add a new test
- Create test file:
backend/tests/test_my_feature.py
pythonimport pytest
def test_my_feature():
"""My feature works correctly."""
result = my_function("input")
assert result == "expected"
- Run:
bashcd backend
pytest tests/test_my_feature.py -v
11. Troubleshooting development issues
| Issue | Cause | Fix |
|---|---|---|
| Port 8765 busy | Stale backend process | Kill Python, restart |
| Vite HMR not working | Browser cache | Hard refresh (Ctrl+Shift+R) |
| Backend not starting | Python not on PATH | Check python --version |
| Models not loading | Missing models/ dir | Run launcher setup |
| Frontend build fails | Node modules stale | rm -rf node_modules && npm install |
| Type errors | TypeScript strict mode | Fix types or add // @ts-ignore |
| Tests fail | ChromaDB path conflict | Use tmp_path fixture |
12. Development best practices
Code style
Python (backend):
- PEP 8 style guide
- Type hints for function signatures
- Docstrings for public functions
TypeScript (frontend):
- Functional components (React Hooks)
- Props interfaces for components
async/awaitover.then()chains
Git workflow
bash# Create feature branch
git checkout -b feature/my-feature
# Make changes, commit often
git add .
git commit -m "feat: add my feature"
# Push
git push origin feature/my-feature
# Open PR on GitHub
Before committing
bash# Type check
cd frontend && npm run typecheck
# Lint
cd frontend && npm run lint
# Tests
cd backend && pytest tests/ -v
# Security checks
npm run check:security
13. Related documentation
| Doc | Coverage |
|---|---|
TESTING.md | Test suite, fixtures, running tests |
BENCHMARKING.md | Performance benchmarks |
TROUBLESHOOTING.md | Common failures |
ARCHITECTURE.md | System overview |
BACKEND.md | API reference |
FRONTEND.md | UI architecture |
CONFIGURATION.md | All env vars & settings |