📚 Docs / Development — Workflows, Debugging & Tools

Development — Workflows, Debugging & Tools

Developer guide for working on Lawyer Assistant. Covers development environment setup, debugging strategies, hot reload, database inspection, and performance profiling.

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:

  1. Starts Electron main process
  2. Electron spawns Python backend (port 8765)
  3. Electron spawns Vite dev server (port 5173)
  4. Electron loads Vite URL in BrowserWindow

2. Development modes

Mode 1: Full app (Electron)

bashnpm run dev  # or: npm start

Use when:

Logs:

Mode 2: Web-only (browser)

bashnpm run dev:web

Starts backend + frontend in parallel (no Electron).

Use when:

Access: http://localhost:5173

Limitations:

Mode 3: Backend-only

bashnpm run dev:backend

Starts only the FastAPI server (port 8765).

Use when:

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:


3. Hot reload behavior

Frontend (Vite + React Fast Refresh)

Hot Module Replacement (HMR):

Full reload triggers:

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:

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:

  1. Open Electron app
  2. Press Cmd+Option+I (Mac) or Ctrl+Shift+I (Windows)
  3. 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:

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:

  1. Open DevTools → Profiler tab
  2. Click ⏺️ Record
  3. Interact with app
  4. Stop recording
  5. Inspect flame graph (which components re-rendered)

Chrome DevTools Performance:

  1. DevTools → Performance tab
  2. Record → interact → stop
  3. See JavaScript execution, rendering, painting

7. Log file locations

LogLocationPurpose
Backendlogs/legal_retrieval.logRotating file (10MB, 5 backups)
Launcherlauncher/logs/app-*.logApp launch logs
Ollama servelauncher/logs/ollama-serve.logOllama daemon logs
Electron consoleTerminal stdout/stderrBackend 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
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

  1. Backend: backend/main.py
python@app.get("/api/my-feature")
async def my_feature(param: str):
    result = do_something(param)
    return {"result": result}
  1. 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();
}
  1. UI: frontend/src/App.tsx
typescriptconst handleClick = async () => {
  const result = await api.myFeature("test");
  console.log(result);
};

Add a new environment variable

  1. Backend: backend/legal_retrieval/config.py
python@dataclass(frozen=True)
class MyConfig:
    my_setting: int = int(os.environ.get("PLR_MY_SETTING", "42"))
  1. Usage:
bashexport PLR_MY_SETTING=100
python backend/main.py

Add a new test

  1. 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"
  1. Run:
bashcd backend
pytest tests/test_my_feature.py -v

11. Troubleshooting development issues

IssueCauseFix
Port 8765 busyStale backend processKill Python, restart
Vite HMR not workingBrowser cacheHard refresh (Ctrl+Shift+R)
Backend not startingPython not on PATHCheck python --version
Models not loadingMissing models/ dirRun launcher setup
Frontend build failsNode modules stalerm -rf node_modules && npm install
Type errorsTypeScript strict modeFix types or add // @ts-ignore
Tests failChromaDB path conflictUse tmp_path fixture

12. Development best practices

Code style

Python (backend):

TypeScript (frontend):

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

DocCoverage
TESTING.mdTest suite, fixtures, running tests
BENCHMARKING.mdPerformance benchmarks
TROUBLESHOOTING.mdCommon failures
ARCHITECTURE.mdSystem overview
BACKEND.mdAPI reference
FRONTEND.mdUI architecture
CONFIGURATION.mdAll env vars & settings

Questions, answered

Short, self-contained answers about this guide.

How do I run the app from source for development?

From the repo root run npm run dev, which starts the FastAPI backend (port 8765), the Vite dev server (port 5173), and the Electron shell together. You can also run the backend and frontend separately in their own folders.

Does the frontend hot-reload?

Yes. Vite + React Fast Refresh gives sub-second hot module replacement — edit a component and it re-renders without a page reload, preserving the Zustand store state. The Python backend does not auto-reload because model loading is expensive.

How do I debug a slow retrieval step?

Use the profiling workflow documented here — it profiles chunking, embedding, retrieval, and generation separately so you can pinpoint which stage dominates latency on your corpus.