# API, Web Review Flow & Extension Boundaries

> The closing operational map for the FastAPI backend, GitHub PR cache, diff Q&A and auto-review RLMs, React diff viewer, streaming responses, provider-key boundaries, and what developers should inspect before extending the system.

- Repository: AsyncFuncAI/AsyncReview
- GitHub: https://github.com/AsyncFuncAI/AsyncReview
- Human wiki: https://grok-wiki.com/public/wiki/asyncfuncai-asyncreview-fb80a82d8c3a
- Complete Markdown: https://grok-wiki.com/public/wiki/asyncfuncai-asyncreview-fb80a82d8c3a/llms-full.txt

## Source Files

- `cr/server.py`
- `cr/github.py`
- `cr/diff_rlm.py`
- `cr/diff_types.py`
- `cr/suggestions.py`
- `web/src/App.tsx`
- `web/src/components/DiffViewer.tsx`
- `web/src/components/ChatPanel.tsx`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [cr/server.py](cr/server.py)
- [cr/github.py](cr/github.py)
- [cr/diff_rlm.py](cr/diff_rlm.py)
- [cr/diff_types.py](cr/diff_types.py)
- [cr/suggestions.py](cr/suggestions.py)
- [cr/config.py](cr/config.py)
- [cr/rlm_runner.py](cr/rlm_runner.py)
- [web/src/App.tsx](web/src/App.tsx)
- [web/src/components/DiffViewer.tsx](web/src/components/DiffViewer.tsx)
- [web/src/components/ChatPanel.tsx](web/src/components/ChatPanel.tsx)
- [web/src/types.ts](web/src/types.ts)
- [web/vite.config.ts](web/vite.config.ts)
- [tests/test_server.py](tests/test_server.py)
- [tests/test_diff_rlm.py](tests/test_diff_rlm.py)
- [README.md](README.md)
- [INSTALLATION.md](INSTALLATION.md)
</details>

# API, Web Review Flow & Extension Boundaries

This page maps the operational path from a GitHub pull request URL to the local web review experience: PR loading, cached file retrieval, diff rendering, automated review, diff Q&A, streaming RLM progress, and follow-up suggestions. It is intended as the closing developer map for extending AsyncReview without crossing the wrong boundary between frontend state, backend API contracts, GitHub data access, and model-provider configuration.

Implementation claims below are grounded in repository code. The selected Compound Engineering guidance was used as page-shaping guidance from the provided bundled snapshot metadata, not as an installed local workflow; no local `STRATEGY.md` or `docs/solutions/**` source is cited because those source classes were not present in this checkout.

## Product Flow First

The web flow starts with a user entering a GitHub PR URL. The React app posts it to the backend, receives a `reviewId` and PR metadata, selects the first changed file, starts an automated review, and lets the user inspect files in a diff viewer. The chat panel sends questions about the whole PR or the current diff selection to a streaming endpoint, then renders RLM iteration steps followed by answer blocks. Sources: [web/src/App.tsx:107-133](), [web/src/App.tsx:139-164](), [web/src/App.tsx:374-393](), [web/src/components/ChatPanel.tsx:189-215]()

The backend owns GitHub access and model calls. The frontend does not receive provider keys or GitHub tokens; it talks to local `/api/*` routes through Vite's proxy during development. The backend reads model names and tokens from environment-backed config, which keeps BYOK behavior on the server side. Sources: [web/vite.config.ts:4-15](), [cr/config.py:9-14](), [cr/config.py:27-33](), [cr/github.py:39-47]()

```mermaid
flowchart LR
  subgraph UI["React web UI"]
    App["App.tsx\nPR URL, tabs, review state"]
    DiffViewer["DiffViewer.tsx\nfile contents, selection, annotations"]
    ChatPanel["ChatPanel.tsx\nSSE chat + RLM timeline"]
  end

  subgraph API["FastAPI backend"]
    Server["cr/server.py\nHTTP + SSE routes"]
    GitHub["cr/github.py\nPR cache + GitHub API"]
    DiffRLM["cr/diff_rlm.py\nDiffQARLM + FastAutoReview"]
    Suggestions["cr/suggestions.py\nfollow-up prompts"]
  end

  subgraph External["External boundaries"]
    GitHubAPI["GitHub REST API"]
    DSPy["DSPy LM provider\nMAIN_MODEL / SUB_MODEL"]
    Deno["Deno PythonInterpreter\nRLM execution"]
  end

  App -->|POST /api/github/load_pr| Server
  App -->|POST /api/diff/review| Server
  DiffViewer -->|GET /api/github/file| Server
  ChatPanel -->|POST /api/diff/ask/stream| Server
  ChatPanel -->|POST /api/suggestions| Server
  Server --> GitHub
  Server --> DiffRLM
  Server --> Suggestions
  GitHub --> GitHubAPI
  DiffRLM --> DSPy
  DiffRLM --> Deno
```

Sources: [cr/server.py:109-190](), [cr/server.py:219-311](), [cr/github.py:50-80](), [cr/diff_rlm.py:190-208](), [web/src/components/DiffViewer.tsx:96-133]()

## Backend API Surface

| Route | Method | Owner | Purpose | Key dependency |
|---|---:|---|---|---|
| `/api/github/load_pr` | `POST` | `api_load_pr` | Parse and load GitHub PR metadata, files, commits, and comments into a cached `PRInfo` | `load_pr` |
| `/api/github/file` | `GET` | `api_get_file` | Fetch base and head contents for one path by `reviewId` | `get_file_contents` |
| `/api/diff/ask` | `POST` | `api_ask` | Non-streaming diff Q&A returning answer blocks and citations | `DiffQARLM.ask` |
| `/api/diff/review` | `POST` | `api_review` | Automated review returning issues and summary | `FastAutoReview.review` |
| `/api/diff/ask/stream` | `POST` | `api_ask_stream` | SSE version of diff Q&A with iteration, block, citation, and completion events | `DiffQARLM.ask_stream` |
| `/api/suggestions` | `POST` | `api_suggestions` | Generate short follow-up suggestions, with static fallback on error | `SuggestionGenerator` |
| `/health` | `GET` | `health` | Minimal service status | none |

Sources: [cr/server.py:109-187](), [cr/server.py:190-216](), [cr/server.py:287-317]()

The API layer is thin by design: request DTOs are Pydantic models, backend domain objects are dataclasses, and response models serialize dataclass `to_dict()` output. Errors from invalid PR URLs become `400`, missing review/file state becomes `404`, and unexpected processing failures become `500` except suggestions, which intentionally falls back to generic prompts. Sources: [cr/server.py:51-107](), [cr/server.py:113-119](), [cr/server.py:128-137](), [cr/server.py:167-187](), [cr/server.py:207-216]()

## GitHub PR Cache and File Retrieval

`cr/github.py` is the boundary for GitHub REST access. `load_pr()` parses a GitHub PR URL, creates a short UUID-based `review_id`, fetches PR metadata, changed files, commits, and issue comments, converts them into a `PRInfo`, and stores it in the process-local `_pr_cache`. This cache is explicitly in-memory, so `reviewId` values are not durable across backend restarts or multi-process deployments. Sources: [cr/github.py:16-17](), [cr/github.py:20-36](), [cr/github.py:50-80](), [cr/github.py:82-129](), [cr/github.py:142-169]()

File retrieval is intentionally lazy. The PR load response includes changed file metadata and patches, but the diff viewer asks for a single file's base and head contents only after that file is selected. `get_file_contents()` uses the cached PR's `base_sha` and `head_sha` to call GitHub contents endpoints twice, returning `None` on either side for added or deleted files. Sources: [cr/github.py:177-235](), [web/src/components/DiffViewer.tsx:96-133]()

### Cache Shape

```text
load_pr(prUrl)
  -> PRInfo(reviewId, repo, baseSha, headSha, files, commitsList, comments)
  -> _pr_cache[reviewId]

get_file_contents(reviewId, path)
  -> oldFile from baseSha, if it exists
  -> newFile from headSha, if it exists
```

Sources: [cr/diff_types.py:104-150](), [cr/github.py:166-174](), [cr/github.py:190-235]()

## Diff Q&A RLM

`DiffQARLM` is the user-driven Q&A engine. It configures a DSPy `RLM` lazily, using `MAIN_MODEL` for the main LM, `SUB_MODEL` as the sub-LM, and a `PythonInterpreter` backed by the Deno command from `build_deno_command()`. The RLM signature accepts diff context, PR info, selection, conversation, and question, and returns answer text plus citations. Sources: [cr/diff_rlm.py:181-208](), [cr/rlm_runner.py:26-70]()

The non-streaming path fetches only the first five PR files when explicit contexts are not provided. The streaming path fetches up to fifty files in parallel, builds both prompt-visible `DiffFileContext` values and a `file_data` dictionary injected into the interpreter, then manually drives RLM internals to emit `RLMIteration` objects before yielding the final answer blocks and citations. Sources: [cr/diff_rlm.py:210-278](), [cr/diff_rlm.py:280-345](), [cr/diff_rlm.py:375-442]()

The backend parses answer markdown into `AnswerBlock` values and parses citations from either dictionaries or string forms such as `path:10-20`. Tests cover modified, added, deleted, and patch-only context generation, citation parsing, and markdown/code block splitting. Sources: [cr/diff_rlm.py:111-178](), [tests/test_diff_rlm.py:17-98](), [tests/test_diff_rlm.py:100-166]()

## Streaming Response Contract

The SSE endpoint returns `EventSourceResponse` from `_stream_ask_response()`. Event payloads are JSON inside `data:` lines and use a `type` discriminator:

| Event type | Meaning | Frontend behavior |
|---|---|---|
| `start` | The backend accepted the question | Logged only |
| `iteration` | One RLM reasoning/code/output step | Added to the current assistant message timeline |
| `block` | One final answer block | Appended to assistant content |
| `citations` | Final citation list | Emitted by backend, but currently not handled in `ChatPanel` |
| `complete` | Stream is complete | Emitted by backend |
| `error` | Backend exception during stream | Shown as chat error |

Sources: [cr/server.py:219-284](), [cr/server.py:287-311](), [web/src/components/ChatPanel.tsx:229-295](), [web/src/components/ChatPanel.tsx:297-322]()

A developer extending citations should inspect both sides: the backend already streams a `citations` event, while the chat panel currently processes `start`, `iteration`, `block`, and `error` but not `citations`. The `onCitationClick` prop is also accepted and intentionally unused with a TODO, so clickable answer citations are an unfinished integration boundary. Sources: [cr/server.py:271-281](), [web/src/components/ChatPanel.tsx:169-176](), [web/src/components/ChatPanel.tsx:252-288]()

## Automated Review RLM

`FastAutoReview` is separate from `DiffQARLM`. It uses `SUB_MODEL` and a DSPy `ChainOfThought` predictor over `ReviewSignature`, not the iterative RLM loop. It builds context from PR patches for up to one hundred files, asks for JSON-like issues plus a summary, parses the returned list, and converts items into `ReviewIssue` dataclasses. Sources: [cr/diff_rlm.py:446-475](), [cr/diff_rlm.py:487-533](), [cr/diff_rlm.py:535-574]()

The instruction block strongly constrains automated-review citations to visible diff hunks. This is important because the frontend validates citation line numbers against loaded old/new file contents before creating line annotations, and invalid citations are dropped from the annotation list. Sources: [cr/diff_rlm.py:493-521](), [web/src/components/DiffViewer.tsx:227-275]()

One extension caveat: `FastAutoReview` asks the model for categories including `bug`, and `App.tsx` separates `bug` issues from flags, but the backend `ReviewIssue` type annotation lists only `investigation` and `informational`. Runtime Python will still carry the string, but type hints and generated assumptions are inconsistent. Inspect this before tightening validation or adding API schemas around review issues. Sources: [cr/diff_types.py:81-90](), [cr/diff_rlm.py:493-500](), [web/src/App.tsx:178-180](), [web/src/types.ts:99-106]()

## React Diff Viewer Boundary

`DiffViewer` owns file-content loading and translation into `@pierre/diffs/react` props. It fetches `/api/github/file` when `reviewId` or selected path changes, converts backend `FileContents` to Pierre file objects, synthesizes empty old/new files for added/deleted paths, and passes line selection and annotations to `MultiFileDiff`. Sources: [web/src/components/DiffViewer.tsx:96-162](), [web/src/components/DiffViewer.tsx:281-286](), [web/src/components/DiffViewer.tsx:449-471]()

Selection flows upward from `DiffViewer` to `App`, then down into `ChatPanel`. A selected line range becomes a `DiffSelection` with `path`, `side`, `startLine`, `endLine`, and `mode`; the chat panel includes that selection in the streaming ask request. Sources: [web/src/components/DiffViewer.tsx:164-178](), [web/src/App.tsx:90-92](), [web/src/App.tsx:239-246](), [web/src/components/ChatPanel.tsx:203-214]()

Issue annotations flow the other direction. Auto-review issues live in `App`, are split into Bugs and Flags tabs, and are passed into the diff viewer. Clicking an issue uses its first citation to switch files, highlight lines, and focus the inline annotation popover after scrolling. Sources: [web/src/App.tsx:94-105](), [web/src/App.tsx:166-180](), [web/src/App.tsx:239-246](), [web/src/components/DiffViewer.tsx:37-94]()

## Provider-Key and Portability Boundaries

AsyncReview is BYOK-oriented in this implementation: model and GitHub credentials are environment variables read by the Python backend, while the web client uses relative API paths. Defaults are Gemini model strings, but `MAIN_MODEL` and `SUB_MODEL` are env-configurable DSPy LM identifiers, so a provider change should be treated as a backend configuration and DSPy compatibility concern rather than a React concern. Sources: [cr/config.py:9-14](), [cr/diff_rlm.py:190-208](), [cr/diff_rlm.py:461-469](), [web/src/components/ChatPanel.tsx:200-215]()

GitHub access is similarly backend-contained. `GITHUB_TOKEN` is read in config and attached only to GitHub API headers; frontend requests identify loaded PRs by `reviewId`, not by token or provider key. This keeps the web flow portable across file, repository, or catalog skill sources as long as those sources are adapted behind backend ingestion and retrieval APIs rather than exposed directly to the browser. Sources: [cr/config.py:27-29](), [cr/github.py:39-47](), [cr/github.py:172-235]()

Installation docs confirm the local server/web split: the FastAPI server runs at `127.0.0.1:8000`, the web UI at `localhost:3000`, and Deno is required for sandboxed code execution. Sources: [INSTALLATION.md:13-19](), [INSTALLATION.md:106-125]()

## What to Inspect Before Extending

| Extension goal | Inspect first | Reason |
|---|---|---|
| Add a new API route | `cr/server.py` request/response models and exception patterns | API DTOs are centralized and thin wrappers over backend modules |
| Change PR metadata | `cr/github.py`, `cr/diff_types.py`, `web/src/types.ts` | Metadata crosses GitHub ingestion, backend serialization, and frontend typing |
| Make streamed citations clickable | `_stream_ask_response`, `ChatPanel`, `App.handleCitationClick` | Backend emits citations, but chat currently ignores that event |
| Improve automated review categories | `ReviewIssue`, `FastAutoReview`, `App` issue filters | `bug` is used by UI/model instructions but not listed in the Python type annotation |
| Change model provider or model names | `cr/config.py`, `cr/diff_rlm.py`, `cr/suggestions.py` | Provider choice is expressed through DSPy LM strings and environment variables |
| Change diff rendering behavior | `DiffViewer.tsx`, `web/src/types.ts` | Selection, highlighting, annotations, and file content normalization are local to the viewer |
| Scale beyond one backend process | `cr/github.py` cache | The current PR cache is process-local memory |

Sources: [cr/server.py:51-107](), [cr/github.py:16-17](), [cr/diff_types.py:104-150](), [web/src/types.ts:10-48](), [web/src/components/ChatPanel.tsx:169-176]()

## Test Coverage Landmarks

Server tests cover the health endpoint, PR loading, file retrieval for normal and added files, missing review handling, Q&A response shape, and automated review response shape. Diff RLM tests cover context construction, citation parsing, and answer block parsing. These are good first checks after changing API contracts or parsing logic, but they do not fully exercise live GitHub, live model calls, or browser SSE rendering. Sources: [tests/test_server.py:17-169](), [tests/test_diff_rlm.py:17-166]()

## Closing Summary

The main extension rule is to preserve boundaries: React owns interaction state and diff presentation, FastAPI owns transport and serialization, `cr/github.py` owns GitHub access and the process-local PR cache, and `cr/diff_rlm.py` owns model-driven review and Q&A behavior. Before adding features, trace whether the change belongs to file ingestion, typed DTOs, streamed events, diff rendering, or provider configuration, then update both sides of any contract that crosses that boundary. Sources: [cr/server.py:109-190](), [cr/github.py:166-235](), [cr/diff_rlm.py:181-208](), [web/src/components/DiffViewer.tsx:96-178](), [web/src/components/ChatPanel.tsx:189-328]()
