CodeNib Web UI¶
For one local repository, use the packaged two-command path in the Quickstart:
This guide covers the advanced multi-repository development deployment. It has two halves sharing one backend:
- Wiki — an auto-generated page tree per repo with source-grounded prose, a per-page interactive code graph (the page rendered as a view over the symbol graph), and a launchable full-screen dependency explorer.
- Ask — the agent QA flow: ask a question about a repo, get an answer whose inline code spans become clickable citation chips that scroll and highlight the code panel.
The stack is a FastAPI backend (codenib.web.app, package codenib/web/)
plus a React/Vite frontend (web/). Release wheels embed the compiled SPA;
Wiki content is generated by codenib/wiki/.
Interactive graph
Earlier versions of this demo embedded Mermaid diagrams. The wiki now
leads with an interactive Cytoscape graph, and the page strips any
LLM-generated Mermaid before rendering with stripGeneratedDiagrams().
Diagrams are no longer the graph surface — the live, clickable Cytoscape
graph is.
Prerequisites¶
- A CodeNib source checkout with development dependencies (
make dev). - Node.js
^20.19.0or>=22.12.0(only for frontend source development, matching the checked-in Vite toolchain). Install deps once withmake web-deps. - An LLM provider reachable through LiteLLM, for Ask answers and generated wiki pages:
OPENAI_API_KEYfor the defaultgpt-4o(a non-thinking model; setmodelinqa_config.yamlorCODENIB_DEMO_MODELto change it —openai/gpt-4o-miniis only the narrator's last-resort fallback when no model is configured anywhere), or- gcloud Application Default Credentials for
vertex_ai/gemini-2.5-flash(gcloud auth application-default login). The demo reads the project from the ADCquota_project_id; no extra env vars are required.
The site still loads repository lists, source views, graphs, and deterministic wiki pages without a usable model provider. Model-backed Ask requests fail, and generated wiki prose degrades to deterministic, source-grounded text.
1. Build (or reuse) the index¶
The server reads a registry written by the build script:
This selects instances from the CodeNib Base dataset
(fishmingyu/codenib-base-dataset on the Hub), checks out each repo at
its base_commit, builds indexes under data_dir (default .codenib_qa/),
and writes qa_registry.json.
Reusing pre-built indexes. If a read-only tree of per-instance artifacts
already exists (layout <dir>/<instance_id>/{repo,l0,l2}/...), point
prebuilt_dir at it to skip cloning and embedding — only BM25 is built locally.
Set it in qa_config.yaml or via env:
The path is fully configurable; nothing is hardcoded to a specific machine.
Native vector reuse also requires a secure source fingerprint v2 in the repository manifest. The interactive Web service treats a missing or legacy fingerprint as an unavailable vector view: it does not open the vector artifact, logs a warning, and continues with BM25 retrieval. Once a v2 fingerprint is present, authorization rejection and artifact-integrity failures still fail closed. Rebuild both the manifest and vector artifact with the current CodeNib version to restore hybrid retrieval; do not edit fingerprint fields by hand.
2. Launch the backend¶
From the repository root (so the relative data_dir resolves):
# autoreload during dev:
uvicorn codenib.web.app:app --host 127.0.0.1 --port 8000 --reload
# or the console script:
codenib-web --reload # honors CODENIB_DEMO_HOST/PORT
The codenib-web console script maps to codenib.web.app:main, which reads
CODENIB_DEMO_HOST (default 127.0.0.1) and CODENIB_DEMO_PORT (default
8000). Index loading takes ~20s. Check it is up:
Configuration and env overrides¶
Config lives in qa_config.yaml (override path with CODENIB_DEMO_CONFIG). A
profile may set extends to one relative parent path or an ordered list of
parents. Parents are applied left to right, the child wins, nested mappings are
merged, and environment variables beat the final YAML (load_config() in
codenib/web/config.py). Missing parents and inheritance cycles fail at
startup instead of silently selecting defaults.
The checked-in templates support two generation profiles without duplicating index and cache settings:
| Profile | Template | Generation route |
|---|---|---|
| Local serve | qa_config.local.yaml.example |
OpenAI-compatible local Qwen/vLLM endpoint |
| API service | qa_config.api.yaml.example |
DeepSeek hosted API for Ask and Wiki |
Copy them to the ignored qa_config.local.yaml and qa_config.api.yaml files.
The API profile extends the local profile, so both routes use the same source
registry, retrieval artifacts, embeddings, and Wiki cache. Set
CODENIB_DEMO_PROFILE=api with scripts/start_web.sh, or point a service
directly at CODENIB_DEMO_CONFIG=qa_config.api.yaml. Keep API keys in
CODENIB_DEMO_API_KEY or the provider's standard environment variable.
Environment overrides are:
| Config key | Env override | Purpose |
|---|---|---|
model |
CODENIB_DEMO_MODEL |
LiteLLM model for the Ask agent (default gpt-4o) |
wiki_model |
CODENIB_DEMO_WIKI_MODEL |
Separate model for wiki prose and edge labels; unset → uses model |
model_api_base / model_api_key |
CODENIB_DEMO_API_BASE / CODENIB_DEMO_API_KEY |
OpenAI-compatible endpoint for the Ask model; provider-native models (Vertex, Anthropic) leave these unset |
wiki_api_base / wiki_api_key |
CODENIB_DEMO_WIKI_API_BASE / CODENIB_DEMO_WIKI_API_KEY |
Optional endpoint and credential dedicated to Wiki generation |
model_options |
CODENIB_DEMO_MODEL_OPTIONS |
Provider-specific LiteLLM options for Ask; the environment value is a JSON object and overrides matching YAML fields |
wiki_model_options |
CODENIB_DEMO_WIKI_MODEL_OPTIONS |
Nested overrides applied only to Wiki, narration, and edge-label calls |
data_dir |
CODENIB_DEMO_DATA_DIR |
Where checked-out repos, indexes, and the registry live (default .codenib_qa/) |
prebuilt_dir |
CODENIB_DEMO_PREBUILT_DIR |
Read-only tree of pre-built per-instance artifacts (see above) |
embedding_provider |
CODENIB_EMBEDDING_PROVIDER |
huggingface (in-process) or openai (BYO endpoint) |
embedding_model / embedding_base_url / embedding_api_key |
CODENIB_EMBEDDING_MODEL / CODENIB_EMBEDDING_BASE_URL / CODENIB_EMBEDDING_API_KEY |
Embedding model plus the endpoint and credential for the remote provider |
edge_labels |
CODENIB_EDGE_LABELS |
Opt-in LLM-written edge phrases in the graph view (off by default; each first-seen edge costs one small LLM call, then cached) |
edge_label_model |
CODENIB_EDGE_MODEL |
Optional cheaper model for the short edge-label calls |
The manifest's embedding route is authoritative when the service reopens a
vector view. Runtime configuration may provide its credential, but cannot swap
the artifact to another provider, model, dimension, or endpoint. A BYO
credential can be copied into CODENIB_EMBEDDING_API_KEY through the
codenib wiki --embedding-api-key-env option. No credential is persisted in
the manifest or vector-store configuration.
When wiki_model and model use the same LiteLLM provider prefix, Wiki may
reuse model_api_base and model_api_key. A different provider never inherits
the Ask endpoint or credential: configure wiki_api_base / wiki_api_key, or
leave them unset for provider-native routing and credentials. For example, a
vertex_ai/... Wiki model will not be sent to an openai/... Ask server.
LiteLLM provider selection comes from the model prefix, for example
anthropic/..., vertex_ai/..., ollama/..., or openrouter/.... CodeNib
does not infer request parameters from a model name. Put non-secret
provider-specific parameters in the option mappings:
model: vertex_ai/gemini-2.5-flash
model_options:
vertex_project: my-project
vertex_location: us-central1
wiki_model_options:
timeout: 60
For an OpenAI-compatible Qwen endpoint whose chat template supports the setting:
model: openai/qwen3
model_api_base: http://127.0.0.1:8080/v1
model_options:
extra_body:
chat_template_kwargs:
enable_thinking: false
The codenib wiki and codenib doctor commands accept the repeatable CLI
equivalent
--model-option extra_body.chat_template_kwargs.enable_thinking=false.
codenib-web itself has no --model-option flag; use the YAML or environment
settings above. CodeNib rejects options that attempt to replace managed fields
such as model, messages, tools, or api_key. Keep credentials in the
provider's standard environment variables or the dedicated API-key setting.
Managed local dev servers¶
For day-to-day development, prefer the repo-local manager instead of launching
long-lived uvicorn and vite processes by hand:
make web-start # start backend :8000 and frontend :3000
make web-status # show PIDs, cwd, port owners, health, and log paths
make web-logs # print recent backend/frontend logs
make web-stop # stop managed processes
make web-restart # restart managed processes
make web-reclaim # reclaim current-user listeners on :3000/:8000, then restart
PID files and logs live under .codenib_demo/server/:
.codenib_demo/server/backend.log.codenib_demo/server/frontend.log
If localhost:3000 returns Internal Server Error after switching branches or
deleting a worktree, run make web-status. A stale Vite process from an old
cwd is usually visible there. Use make web-reclaim to stop current-user
listeners on the demo ports and restart both services from the current checkout.
The backend exposes (all under /api):
| Endpoint | Purpose |
|---|---|
GET /api/health |
Liveness + repo count |
GET /api/repos |
Repo list (each carries capabilities, e.g. codemap, plus incremental cost stats for repos with a commit window) |
GET /api/repos/{id}/wiki |
Wiki page tree |
GET /api/repos/{id}/wiki/{page} |
A wiki page (markdown + citations) |
GET /api/repos/{id}/wiki/{page}/graph |
The page's subsystem graph (view over the symbol graph) |
GET /api/repos/{id}/commits |
Commit window for the graph view, newest first (available: false without a prebuilt window) |
GET /api/repos/{id}/codemap |
Dependency subgraph around a symbol (symbol, direction, depth, max_nodes, commit) |
GET /api/repos/{id}/source |
Source lines for the code/peek panels |
POST /api/repos/{id}/edge-label |
Short LLM phrase for how a graph edge's source uses its target (opt-in via edge_labels; returns disabled when off) |
POST /api/chat |
Ask: answer + citations |
Interactive requests are bounded before repository or model work:
| Boundary | Public budget |
|---|---|
Ask (POST /api/chat) |
128 messages; 65,536 characters per message; 262,144 characters across the conversation; 512 characters for repo_id |
Edge label (POST /api/repos/{id}/edge-label) |
4,096 characters per file path; 1,024 characters per display label; 128 characters for the commit; 32 call-site anchors with positive line numbers |
| Direct FastAPI request body | 8 MiB, enforced while streaming even without Content-Length |
| Installed same-origin proxy | 8 MiB request body and 32 MiB upstream response body |
The browser sends only the first three edge-label anchors because prompt
generation consumes at most that sample. The installed proxy rejects
transfer-encoded request bodies and malformed, negative, or oversized
Content-Length values instead of buffering them. Validation errors omit the
rejected input so oversized prompts are not reflected back to clients.
The /commits endpoint and the commit parameter are served from per-commit
graph snapshots — see Per-commit graph snapshots
below.
3. Launch the frontend¶
Open http://localhost:3000. With the default or a
loopback configuration, browser requests stay same-origin on :3000: the
Vite server proxies /api/* to the backend at http://127.0.0.1:8000
(web/vite.config.ts). Override that server-side target with
CODENIB_API_BASE. The production Python server follows the same same-origin
contract: it proxies /api/* to the local FastAPI process and leaves
/runtime-config.js relative by default. This also keeps a Wiki bound to
0.0.0.0 usable from another machine without exposing a loopback API URL to
that browser.
For a long-lived reverse-proxied demo, build once and serve the production bundle instead of leaving Vite's development server running:
cd web
npm run build
CODENIB_API_BASE=http://127.0.0.1:8000 \
npm run preview -- --host 127.0.0.1 --port 3001
The document base keeps relative Vite chunks rooted correctly when a browser
opens or refreshes a nested route such as /<repo>/ask. Static export removes
that document base and rewrites the same assets to its configured Pages mount,
so sub-path exports remain portable.
What the loading states mean¶
- The route-level
Loading CodeNib…is a lazily loaded frontend chunk. A production build avoids the development transform and React StrictMode request replay costs. - A Wiki page is normally a disk- or memory-cache read. On its first load, the backend retrieves source evidence and may ask the configured Wiki model to generate prose. Concurrent requests for the same cold outline or page are coalesced into one generation call.
- A degraded fact-plan fallback is retained as diagnostic evidence, but it is
not treated as durable page content. A fresh backend process retries that
page from the original evidence, and the reader UI withholds the internal
fallback instead of displaying
is indexed frominventory text. - Ask is a multi-turn model run. Retrieval itself is usually short; model
turns and the growing evidence context dominate latency.
max_turnsis the main latency/coverage trade-off, so tune it with repeated, source-checked questions rather than a ping prompt. - Source panes and dependency graphs load independently after the narrative; they do not need to hold the page in a generic loading state.
- Repository cards inspect the small leading schema field of
graph.pklwithout loading the graph. An older persisted schema disablescodemapimmediately; the page-graph API returns the required rebuild version instead of advertising an indexed graph and then returning an unexplained empty map.
The Wiki and Ask views replace a bare spinner with the active stage and elapsed
time. A direct Wiki deep link starts with its requested page instead of first
fetching overview. API responses also expose backend processing time through
the Server-Timing: codenib;dur=... header. Requests that spend at least two
seconds in the backend are logged by method, path, and duration; query strings
and Ask contents are deliberately omitted.
Audit the Wiki cache¶
The cache auditor reads only entries reachable from the current registry and outline identities; it never generates an outline or page and therefore never calls the Wiki model:
Its JSON report separates Overview, root-page, and child-page coverage; lists
degraded, fallback, and quality-invalid pages; and reports stale/orphaned Wiki
database entries and payload bytes. Newly generated pages also contribute
retrieval, planning,
model-call, repair-count, and total-latency summaries. Existing cache entries
created before those metrics were introduced remain readable and are simply
excluded from the latency sample. Add --fail-on-fallback and
--fail-on-quality-invalid to use the report as an acceptance gate.
Prewarm the landing page for every registered repository with a small bounded worker pool after reviewing the selection in dry-run mode:
make wiki-cache-prewarm \
WIKI_CACHE_PREWARM_ARGS="--config qa_config.local.yaml --scope overview --dry-run"
make wiki-cache-prewarm \
WIKI_CACHE_PREWARM_ARGS="--config qa_config.local.yaml --scope overview --workers 2 --retry-degraded-now --fail-on-error --fail-on-quality-invalid"
overview is the safe public-demo default; root includes every top-level
section and all includes child pages. The prewarmer skips healthy entries and
quality failures still inside their retry cooldown. The sidebar reports each
page as cached, cold, retryable, or needing review; the browser preloads at most
two adjacent pages only when they are already cached, so navigation does not
silently create model spend.
--retry-degraded-now is an explicit operator action: it bypasses the retry
cooldown for degraded pages in this prewarm run without changing the bounded
reader-traffic policy. It does not regenerate healthy cached prose.
The standalone prewarmer groups pages by repository and releases that
repository's BM25/vector views as soon as its group finishes, so a 26-repository
run retains at most the configured worker count of large retrieval bundles.
Pass --keep-views only when profiling retained-view memory deliberately.
Rebuild demo indexes safely¶
When a manifest predates secure source fingerprints, rebuild its BM25, vector, and symbol-graph views together instead of editing the manifest. Preview the registry selection first, then publish either all repositories or a repeated set of repository ids:
make demo-index-rebuild \
DEMO_INDEX_REBUILD_ARGS="--config qa_config.local.yaml --dry-run"
make demo-index-rebuild \
DEMO_INDEX_REBUILD_ARGS="--config qa_config.local.yaml --repo sharkdp__bat"
The command builds into <data_dir>/rebuilds-source-v2, validates the source
binding, commit, fresh-view status, and complete vector artifact, then replaces
only the manifest while holding the compiler cache lock. A demo graph with
fewer than 25 nodes is also rejected, catching tools that exit successfully
after indexing the wrong workspace boundary. The command preserves the original
as repo_manifest.json.pre-source-v2 and refuses publication if the live
manifest changed while the private generation was being built.
Some demo checkouts populate optional dependency SDKs after cloning. Keep a
repository-specific exclusion explicit and persisted in the BM25/vector
artifact identity instead of silently adding that directory to the global
source policy. For example, MicroPython's populated lib/ tree is external
dependency material and otherwise contributes hundreds of thousands of noisy
chunks:
make demo-index-rebuild \
DEMO_INDEX_REBUILD_ARGS="--config qa_config.local.yaml --repo micropython__micropython --ignore-dir micropython__micropython:lib"
The source fingerprint still binds the entire visible checkout; the exclusion only defines the retrieval corpus. Consequently, a dependency-tree change is detected conservatively even though those files are not searchable.
The managed Rust analyzer remains the default. If one repository exposes an
upstream analyzer crash, set CODENIB_RUST_ANALYZER to an explicit validated
executable for that rebuild; this opt-in override does not replace the pinned
toolchain for other repositories.
Compare Ask model candidates¶
With the candidate served behind the configured OpenAI-compatible endpoint, run the fixed Requests, Gin, and Vue source-grounding gate and retain its JSON:
make demo-ask-benchmark \
DEMO_ASK_BENCHMARK_ARGS="--candidate qwen-current --repeat 2" \
> demo-ask-qwen-current.json
The report records end-to-end and backend-reported latency, citation location
validity, expected-file recall, and expected-term coverage. The command exits
nonzero if any case has invalid citation locations or falls below the default
0.3 file-recall or term-coverage threshold. Use --min-file-recall and
--min-term-coverage to raise those per-case thresholds for a release gate.
Compare candidates with identical indexes, backend settings, cases, thresholds,
and repeat counts; a successful model ping is not a demo-quality or latency
result.
Wiki prose has a separate gate because it uses a separate model and quality contract. It reuses each repository's current cached outline, writes candidate pages only to an isolated temporary cache, and retains metrics rather than the generated prose:
make demo-wiki-benchmark \
DEMO_WIKI_BENCHMARK_ARGS="--config qa_config.local.yaml --candidate deepseek-current --repo psf__requests"
make demo-wiki-benchmark \
DEMO_WIKI_BENCHMARK_ARGS="--config qa_config.local.yaml --candidate local-candidate --model openai/local-model --api-base http://127.0.0.1:8080/v1 --repo psf__requests"
Run cache prewarming first: the benchmark intentionally refuses to generate a new outline, so every candidate receives the same source-bound page plan. A run fails when generation errors, falls back to diagnostic mode, or produces a page that does not pass the normal Wiki quality gate.
Use the retained JSON reports to compare Qwen3.6, Qwen3.8-27B, DeepSeek V4 Flash, Muse Glimmer 30B, or another candidate under the same indexes, backend settings, cases, and repeat counts.
Running over SSH¶
With the default or loopback API configuration, the browser talks only to the
Vite dev server on :3000; the dev server proxies /api/* to FastAPI on
the remote host. A single forward is enough:
(Address already in use on reconnect just means an earlier tunnel still
holds the port.) Set CODENIB_API_BASE on the host when the backend uses a
different address.
The Wiki page¶
Each wiki page (web/app/[repoId]/page.tsx) is a three-rail layout: a section
tree on the left, the narrative in the middle, and an "On this page" outline on
the right. The middle column leads with the graph, then the prose:
- Subsystem map. When the page's symbols resolve in the symbol graph, the
page renders a
GraphViewinvariant="wiki"mode inside a<details className="subsystem-map">block whose summary row reads "Subsystem map · N symbols · Last indexed \<sha>". This is built bybuild_page_subgraph()incodenib/web/codemap.py: it resolves each citation to a graph node, takes the page's cited symbols as seeds (highlighted as roots), and returns those nodes plus the direct reference edges between them. It deliberately does not expand a one-hop neighbourhood: the only non-cited symbols admitted are up to two bridge symbols, each of which must touch at least two cited symbols (ranked by call-site evidence), keeping the map grounded in the page's evidence. Edges retain the exact call-site count and carry up to 12 source-linked anchor samples. - Relevant source files. A
<details>list of the distinct repo-relative files cited on the page, each linking to the exact GitHub blob at the indexed commit. - Source-grounded prose. The narrative markdown, rendered after generated Mermaid is stripped (see below). Headings drive scroll-spy in the right rail.
The Refresh this wiki button re-fetches the page tree and active page from
the backend. It does not clear the in-memory or on-disk generation cache, run a
new index build, or force model regeneration. To regenerate model-authored
pages after changing provider settings, run the bounded prewarmer with
--retry-degraded-now; healthy pages remain cached. A deliberate prompt or
index identity change selects new cache entries without deleting unrelated
healthy content.
No more Mermaid¶
The page calls stripGeneratedDiagrams() on the markdown before rendering. It
removes any LLM-generated ```mermaid fence (and a preceding heading left
empty by the removal). The interactive Cytoscape graph replaces the static
diagram entirely. The <Mermaid> component still loads lazily in
web/components/Markdown.tsx for any stray fence, but wiki pages no longer
emit one.
The full-screen Dependency Map¶
The wiki header shows a Dependency Map button. Clicking it opens a
full-screen modal (graph-modal, Esc to close) hosting Codemap
(web/components/Codemap.tsx), the standalone explorer. Repositories without
the codemap capability get setup commands for the optional graph profile
instead of an empty canvas. When the capability is present, the same component
is reachable seeded on a symbol via "Focus here" in the wiki subsystem
map's node peek — that re-roots the explorer on the chosen symbol.
Codemap calls GET /api/repos/{id}/codemap, backed by build_codemap() in
codenib/web/codemap.py:
- Focus symbol. A free-text symbol box re-roots the map; blank picks the
repo's busiest symbol (
_default_seed()= highest reference out-degree function/method/class). - Direction. A dropdown chooses
both(callers + callees),callees(what it calls), orcallers(what calls it). - BFS walks reference edges out from the root (
depthclamped to 1–4,max_nodesclamped to 2–120; the explorer defaults to depth 1 and requests 28 nodes, or 40 at depth 2). - A row of chips below the graph lists every node; clicking a chip re-roots on it.
The modal is the full-screen surface; the wiki subsystem-map is the
inline one. Both render the same GraphView component — the difference is
variant ("explore" vs "wiki") and which payload feeds it.
The interactive graph (Cytoscape)¶
GraphView (web/components/GraphView.tsx) wraps CodeGraph
(web/components/CodeGraph.tsx), a Cytoscape view loaded dynamically (~3 MB of
Cytoscape + layout code) so the narrative paints first. It is a single
top-down dependency view — there is no Flow/Clusters toggle anymore.
Click an edge → exact source¶
Every edge is a real reference (call/use) relation. CodeNib retains the exact
number of distinct LSP/SCIP call sites while serializing at most 12 source
anchors per edge. Clicking an edge opens a source peek (SourcePeek in
GraphView.tsx) at one of those precise lines; the pager states both the total
and displayed sample when an edge is truncated. Anchor lines are exposed
1-based (converted from the 0-based tree-sitter/LSP origin in codemap.py).
Hovering an edge shows the complete call-site count.
Click a node → definition¶
Clicking a node opens its definition in the same source peek (with a "Focus here" shortcut that re-roots the explorer) and puts the node into persistent focus: the node and its neighbourhood pop via an accent border while unrelated edges fade — nodes are never greyed out, so every label stays legible. Esc or a background click clears focus. External symbols (defined outside the repo, no openable source) render dashed/italic and the peek says so instead of fetching source.
Top-down dependency layout¶
Layout is computed by computeFilesPositions() (web/lib/filesLayout.ts), a
pure, unit-tested function where position encodes meaning on both axes:
- Vertical = dependency depth. A file-level dependency graph is layered by longest path (cycle-safe bounded relaxation): callers / entry points sit at the top, their dependencies flow downward, and files that touch nothing else sink to a dedicated bottom band so they don't crowd the entry row.
- Per-file boxes. Each file becomes a compound box (titled with its basename); its symbols stack top→bottom by source line inside it. Boxes are packed into balanced lanes within each band, spaced by each node's measured width/height so wide or wrapped labels never overlap.
- Fallback. When there are no cross-file edges, it falls back to size-packed columns (largest files first, shortest-column-first packing).
Importance, community, and hub collapsing¶
Node and edge encodings come from _score() / _enrich() in
codenib/web/codemap.py, computed on the induced reference subgraph
(≤120 nodes), best-effort (never 500s the map):
- Importance (PageRank). Directed PageRank (damping 0.85) over the reference subgraph, exposed as a rank-percentile in [0, 1]. It drives node size: bigger node = more referenced / more "core" (the UI legend reads "bigger = more referenced").
- Community (Leiden). Modularity-based Leiden clustering on the undirected
projection (igraph 0.11.9 has no directed Leiden), gated so tiny or loose
graphs (< 12 nodes, or modularity < 0.2) collapse to one cluster. Exposed as
each node's
community. - Reference count & entry score.
ref_count= in-degree (how many symbols reference this one);entry_score= out/(in+out) in [0, 1], high for drivers / entry points. - Colour = kind. Node border colour is fixed per symbol kind (function, method, class, interface, struct, …) since position already encodes file + line. Roots render filled blue.
- Edge width = call-site count.
weightis the complete number of distinct call sites even whenanchorsis sampled; a heavily-used call reads thicker. - Hub collapsing. A node with more than
hub_cap(8) outgoing edges keeps only the edges to its highest-importance targets; the rest are folded away and counted inhidden_callees, surfaced in the UI as a "+N" badge on the node label. Nodes left fully disconnected by that fold (and not a root) are dropped, so dense, high-fan-out maps don't become hairballs.
Per-commit graph snapshots¶
By default a repo serves one symbol graph, built at its indexed base_commit.
A repo can additionally carry a commit window — one graph snapshot per
commit for the last N first-parent commits of a ref — built offline by:
python scripts/build_commit_window.py --repo-dir /path/to/repo \
--n 5 --ref origin/main --language python
Only the oldest commit in the window pays a full cold build; the builder
attempts to reach every later commit by patching the previous graph forward
through the LSP-backed incremental patcher. This utility is best-effort: a
language whose server cannot start is omitted, and a failed per-language patch
leaves that language's prior facts in the saved graph. The resulting window is
therefore a demo artifact, not a commit-exact or fresh-rebuild-validated index;
operators should inspect warnings and per-language patch_stats before using
it for comparisons. Snapshots plus a commit_window.json manifest land under
the repository's CodeNib state directory below
$CODENIB_HOME/repositories/.../indexes/commit_window/ by default
(--out-dir overrides), and the backend picks them up per repo — no registry
change needed. With a window present:
- The graph-snapshot selector. The wiki's left rail swaps its static "Last
indexed \<sha>" label for a "Graph snapshot" dropdown fed by
GET /api/repos/{id}/commits(newest first). Picking a commit re-renders the dependency graph through thecommitquery parameter onGET /api/repos/{id}/codemap; with nocommitthe window's newest snapshot is served. This selection is graph-only: source peeks still read the repository checkout configured inRepoEntry.repo_dirand can show a different revision. Repos without a window returnavailable: falseand keep the single-commit label. - Snapshot fallback contract. The codemap response reports the
commitactually served. An unknowncommitvalue is a 404; a snapshot that exists in the manifest but cannot be loaded (e.g. an olderschema_version) falls back to the repo's default graph withfell_back: true, and the explorer says "The selected snapshot could not be loaded. Showing the default graph…" rather than labelling one commit's graph with another commit's SHA. - Warm patch cost. The displayed ratio divides cold graph-build time by
mean warm
patch_files()time. It excludes language-server startup, diff detection, checkout, and snapshot serialization, so it is not an end-to-end re-index speedup. The figures are derived in one place server-side (window_stats()incodenib/web/commit_window.py) and surfaced twice: the landing card shows "N commits · M× warm-patch speedup" (from theincrementalfield onGET /api/repos; just "N commits" when no defensible speedup exists), and the rail under the selector shows a per-commit cost line — "cold build · X.Xs" for the anchor commit, "warm patch · X.XXs · N files changed · ±D nodes" for commits reached incrementally.
The Ask page¶
The Ask flow (web/app/[repoId]/ask/page.tsx) is a two-column "codemap" layout:
the answer on the left, a code panel on the right. Submitting a question
re-fetches POST /api/chat; every submit is its own answer.
- Clickable citation chips. The answer is rendered through
Markdown(web/components/Markdown.tsx) with the run'scitationsand anonCitehandler.matchCitation()(web/lib/citations.ts) conservatively maps each inline code span to the citation it names — by exact symbol name, exact or suffix repo-relative path, file basename, the last segment of a qualified symbol (file.py:sym()), or apath:lineform. Ordinary inline code that matches nothing stays plain. A match renders as aCiteChipbutton; symbol matches also show a:line(or:start-end) badge fromlineLabel(). - Click-to-scroll + highlight. Clicking a chip calls
selectCitation(i), which sets the active citation (setActive) and bumps ascrollSignal. The sharedrefslist (fromcodeRefs(), filtered to repo-relative files and capped at 12) keeps a chip's index aligned with the code panel's fragments, so the right-handCodePanelscrolls to and highlights the cited span. The signal is keyed independently of the active index, so clicking the already-active chip re-scrolls to it. - Question truncation. A long question is clamped to
Q_TRUNCATE(200 chars) with a "Show full text" / "Show less" toggle. - Run metadata. Below the answer: tool-call count, turns, duration, and the number of references.
Troubleshooting¶
| Symptom | Cause / fix |
|---|---|
| Stuck on "Loading repositories…" | Backend unreachable from the Vite proxy or production runtime configuration. Confirm :8000 is up (curl 127.0.0.1:8000/api/health); over SSH only :3000 needs forwarding in source-development mode. Hard-refresh after the backend finishes loading. |
Direct /<repo>/ask refresh is blank |
Rebuild the frontend from a checkout whose index.html contains the live document base. Without it, relative Vite assets resolve below the nested route and return 404. Static exports remove and remount this base automatically. |
Vite says This host is not allowed |
Add the exact public proxy hostname to server.allowedHosts (and preview.allowedHosts when using vite preview). Keep host checking enabled; do not use allowedHosts: true. The checked-in config already admits demo.codenib.ai. |
| Wiki says "Couldn't load this page" | Read the status and backend detail now shown in the page. A provider or generation error points to the Wiki model; a legacy source-fingerprint warning means the Web service is serving BM25 until the manifest and vector artifact are rebuilt. Older or strict runtimes may instead return native vector authorization requires source fingerprint v2. |
| Wiki stays on "Preparing a source-linked page…" | This is a cold page generation, not the Ask model. Check the Wiki provider and its timeout, then inspect whether another identical request is already in flight. Each blocked generation-lock acquisition gives up after 30 seconds without interrupting the owner or starting duplicate work; retry later to reuse the published page. |
| Ask shows a backend error | The configured model endpoint is unreachable, provider authentication is unavailable, or the model call failed. Check the backend log and run codenib doctor --require agent with the same model settings; add --probe-model to send live text, tool, and structured-output probes. |
| Wiki prose looks deterministic after changing models | Source-checked page caches are model-independent by design, so changing the configured model does not rewrite healthy pages. Degraded fact-plan fallbacks are retried automatically by a fresh backend process; clear the matching cache only when intentionally regenerating a healthy page with another model. |
| Frontend changes do not appear | Run make web-status and inspect the Vite process cwd. Stop a current-user process rooted in an old checkout or detached snapshot, then start the frontend from the checkout being edited. Never reclaim a listener owned by another user. |
| Dependency Map shows setup commands | The repo has no usable symbol graph, so the codemap capability is off. Install codenib[graph] and rebuild with codenib wiki . --preset graph. |
| Page graph says its schema is incompatible | The prose and graph are separate artifacts. The page can remain source-cited while an older graph.pkl is rejected. Rebuild symbol_graph with the current CodeNib version; do not edit the pickle's version field. |
repos: 0 at /api/health |
No registry. Run scripts/build_qa_index.py (or fix data_dir/prebuilt_dir). |