feat(web): read a document's converted source text in the Documents pane (#197)#199
Merged
Merged
Conversation
…ane (#197) The Documents pane listed each ingested doc (name/type/hash) with no way to read the converted full text ingestion produced under wiki/sources/. Backend: new POST /api/v1/document/source resolves a doc hash to its source text — short docs read <doc_name>.md; long docs concatenate the per-page <doc_name>.json (page text joined by a thematic break). Hash is the identifier (unique, avoids doc_name/stem collisions); resolution prefers the registry's stored source_path then falls back to the wiki/sources/<doc_name>.{md,json} convention, with a path-traversal guard. Read-only (sources are do-not-edit). Frontend: document rows are now clickable and open a wide read-only slide-out reader (MarkdownView) — ESC/overlay/close to dismiss, independent scroll, content cached + memoized per hash, native find-in-page preserved (no virtualization). Delete stays inline (stopPropagation). Closed drawer is inert. Known limitation: images embedded in long-doc pages are not rendered inline yet.
…197) Correctness / a11y: - Rebuild the reader drawer on Radix Dialog (like KbSettingsSheet) instead of a hand-rolled overlay: proper modal focus trap, initial + return focus, Escape, and background inert (was: aria-modal with none of it) [#4]. This also removes the hand-rolled window keydown listener that re-subscribed every render [#7]. - Restructure each document row so the open-reader target is a real <button> and the delete control is a SIBLING, not nested. Keyboard-activating delete no longer bubbles into opening the reader, and the invalid nested-interactive markup is gone [#1, #5]. - Resolve a source file by the doc's own type (long → .json first, else .md), so two docs sharing a doc_name each resolve to their own file rather than whichever extension is tried first [#2]. - Guard source reads: skip non-dict page entries, reject non-list JSON, and return a controlled 500 on corrupt/unreadable sources instead of an unhandled exception [#3]. - Invalidate the per-hash content cache when the inventory changes, so a reopen after recompile refetches instead of serving stale text [#6]. Fetch/cache/memoized body moved to DocumentsPane so they survive the drawer's unmount-on-close. #8 (frontmatter stripping) intentionally not applied: source docs render verbatim (a user's own frontmatter is content, unlike wiki-page OKF metadata). Adds tests for the collision and malformed-JSON paths.
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.
What
Closes #197 — the Documents pane listed each ingested doc (filename, type, hash) with no way to read the actual full text that ingestion produced under
wiki/sources/. This adds a read-only reader for that converted source text.Backend
POST /api/v1/document/source(openkb/api_documents_router.py+openkb/documents.py, models inapi_models.py). Resolves a document hash to its converted text:wiki/sources/<doc_name>.mdwiki/sources/<doc_name>.json(per-pagelist[{page, content, images}]), pages joined by a thematic breaksource_path, then falls back to thewiki/sources/<doc_name>.{md,json}convention ordered by the doc's type (long →.jsonfirst) so two docs sharing a doc_name each resolve to their own file. Path-traversal guarded.Frontend
MarkdownView), with a read-only badge, filename, type and page count.KbSettingsSheet): focus trap, initial + return focus, Escape, background inert. Body scrolls independently; native find-in-page works (the whole doc is rendered, not virtualized). Content is fetched by hash, cached, and the parsed body is memoized, so re-opening is instant.Known limitation
Images embedded in long-doc pages are not rendered inline yet — they surface as a literal
reference. This is a separate follow-up (needs Markdown image support + awiki/sources/images/*serving route with the token-blob pattern). Note that OpenKB's ingestion also does not caption/OCR images, so figure/chart content does not reach the LLM regardless.Verification
1246 passed(adds tests for short/long/collision/malformed-JSON/404/auth paths); ruff + mypy clean.npm run build(i18n + tsc + vite) green.Review
Ran an xhigh code review over the diff; the 8 findings were addressed in the second commit (keyboard/focus/modal a11y, doc_name-collision resolution, malformed-JSON handling, cache invalidation). One finding (frontmatter stripping) was intentionally not applied — source docs render verbatim, unlike wiki-page OKF metadata.