fix(ui): render document content + entity graph; nil-safe embedder#93
Merged
fix(ui): render document content + entity graph; nil-safe embedder#93
Conversation
Three fixes for issues surfaced when running through the indexed-corpus
flow end-to-end.
Backend
- Add `GET /api/documents/{id}/chunks` returning the ordered chunks for
a document (id, chunk_index, content, token_count). The store already
exposes `ListChunksByDoc`; this is a thin handler around it.
- Add `GET /api/graph?project=...&type=...&limit=...` returning the full
entity graph as `{nodes, edges}` matching the UI's existing
RawGraphResponse shape, so the same GraphCanvas can render it without
a parallel transform.
- Skip the embedding phase in `pipeline.indexFile` when `p.embedder` is
nil (provider=none / graph-only flow). Previously every CLI index run
with `DOCSIQ_LLM_PROVIDER=none` panicked with a nil-pointer in
`(*Embedder).EmbedTexts`, contradicting the CLAUDE.md guarantee that
the embedder is nil-safe in this mode. Chunks are still persisted;
downstream extraction works off raw text rather than vectors.
Frontend
- New `useDocChunks` hook fetching the chunks endpoint above.
- `DocumentView` now renders chunk content. For markdown documents we
pipe through `markdown-it` (already a dep) for HTML; otherwise we
fall back to a `<pre>` of the raw text. The previous view rendered
only `title + doc_type · v<n>`, leaving the document body blank — the
symptom users were hitting after `docsiq index`.
- New `useEntityGraph` hook fetching `/api/graph` and adapting it to
the existing `GraphData` interface.
- `Graph.tsx` now loads both the entity graph (from the indexing
pipeline) and the notes graph (wikilinks between authored notes),
defaults to the entity graph when it has nodes, and exposes a small
toggle so users can flip between the two. The previous route was
hard-wired to `useNotesGraph` only, so an indexed corpus with no
hand-authored notes always showed the empty state.
Adjacent issues surfaced but **not** fixed here (separate PRs):
- `./docsiq index --force` collides on `documents.file_hash UNIQUE`
because the supersede path bumps version but doesn't release the old
hash.
- `/api/graph/neighborhood` requires the entity *name* (case-sensitive
via `GetEntityByName`); UUIDs return 404. Worth accepting either.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three fixes that were surfaced when walking through the full indexed-corpus flow end-to-end.
Backend
GET /api/documents/{id}/chunks— new endpoint returning ordered chunks (id,chunk_index,content,token_count). The store already exposesListChunksByDoc; this is a thin handler.GET /api/graph?project=...— new endpoint returning the full entity graph as{nodes, edges}, shaped to match the UI's existingRawGraphResponse.pipeline.indexFileis now nil-safe on the embedder whenprovider=none. Previously the CLI panicked with(*Embedder).EmbedTextson a nil receiver, contradicting CLAUDE.md's nil-safe contract.Frontend
DocumentViewnow renders chunk content as markdown (viamarkdown-it, already a dep) whendoc_typeismd/markdown, with a<pre>fallback otherwise. Previously the view only renderedtitle + doc_type · v<n>, so any opened document looked blank.Graphnow loads both the entity graph and the notes graph, defaults to the entity graph when it has nodes, and exposes a toggle so users can switch. Previously hard-wired touseNotesGraph, so an indexed corpus with no hand-authored notes always showed the empty state.Verified
CGO_ENABLED=1 go build -tags sqlite_fts5 ./...— clean.go test ./internal/api/... ./internal/pipeline/... ./internal/embedder/...— all green.tsc -b --noEmit && vite build— clean.docsiq serve --port 37777:GET /api/graph?project=_default→ 200, 10 nodes / 7 edges (entities & relationships from the sample corpus).GET /api/documents/{id}/chunks?project=_default→ 200, returns the actual document body chunks in order.Out of scope (separate PRs)
./docsiq index --forcecollides ondocuments.file_hash UNIQUE— the supersede path bumps version but doesn't release the old hash./api/graph/neighborhoodonly accepts the entity name (case-sensitive viaGetEntityByName); UUIDs return 404.Test plan
ci,codeql,security,scorecard,playwright/documents/<id>in the UI — content should render/graphin the UI — entity graph should render with 10 nodes if_defaultis indexed; toggle to Notes graph should work too