From efc2c18a9115e74510a2cf2b1b730052b486dd9e Mon Sep 17 00:00:00 2001 From: jules Date: Sat, 11 Jul 2026 08:51:50 +1000 Subject: [PATCH] =?UTF-8?q?fix(kb-ui):=20make=20the=20object=20reader=20re?= =?UTF-8?q?adable=20=E2=80=94=20reflow,=20window,=20jump-to?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reader dumped the whole server slice verbatim: a ~445k-char doc rendered as a 16,558px wall of one-source-line-per-line ragged text, and the outline panel showed 8 blank rows (the API's sections carry heading:null — they're mechanical 60k-char chunks, not headings). - Reflow: off the citation path, parse the extracted markdown/PDF text into real blocks — drop form-feeds, split on blank lines, join soft-wrapped lines into paragraphs, lift markdown headings + multi-line bullet lists. Cap prose width at 68ch. The citation-highlight path still renders the raw slice verbatim so character offsets stay exact. - Window every load: initial 8k chars, "Read more" +12k, section jump loads a bounded window from the section's start (server honours start/end precisely). Object height 16,558 → ~5,315px; Read more/citations verified. - Outline: blank heading:null rows now render as "Part N" jump points and the panel is titled "Jump to" (vs "Outline") when there are no real headings; aria-current on the active row. Verified live on dev-knowledge.sky-ai.com. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/components-object.tsx | 99 +++++++++++++++++++++++++++++++++------ 1 file changed, 85 insertions(+), 14 deletions(-) diff --git a/src/components-object.tsx b/src/components-object.tsx index 30239a3..d0e179a 100644 --- a/src/components-object.tsx +++ b/src/components-object.tsx @@ -28,6 +28,16 @@ import { isImageMime, } from "./_internal"; +// The reader loads a bounded window and reveals more on demand — a long +// document (this one is ~445k chars) must never render as a single wall. +const INITIAL_CHARS = 8000; +const MORE_CHARS = 12000; + +/** A section's display label — real heading, else a mechanical "Part N". */ +function sectionLabel(s: OutlineSection, index: number): string { + return s.heading?.trim() || `Part ${index + 1}`; +} + // ---- ObjectList ----------------------------------------------------------- export interface ObjectListProps { @@ -222,20 +232,21 @@ export const OutlineNav: FC<{ }> = ({ sections, activeId, onSelect, className }) => { if (sections.length === 0) return null; return ( -