Releases: aREversez/VS-Code-DITA-Viewer
Release list
V1.0.9
DITA Viewer v1.0.9
321 commits since V1.0.8 · August 19 → October 2, 2026
Highlights
Content reuse by key (conkeyref)
conkeyref="brand/phrase" now pulls in the referenced content in every view — topic preview, Book, Docsite, map preview, and Git compare. Key resolution honours the selected key context map; undefined keys fall back to conref per the DITA spec; cycles are detected and terminated.
CJK spacing around English key values
When a keyref resolves to Latin text next to Chinese characters (打开ABC), a space is inserted automatically — both in the preview and in DITA-OT output via the bundled com.dita-viewer.cjk-spacing plugin. Source files are never modified; the plugin operates on DITA-OT's temporary copies and backs up every file it touches before installing.
Docsite view matures into a full reading mode
- Templates: three built-in themes (Classic Docs, Aurora, Reader) with light/dark palettes; add your own via
dita-viewer.templatesDirectory. - Keyboard navigation: sidebar tree is one Tab stop; arrow keys, Home/End, Enter/Space for expand/collapse/open.
- Back / Forward history (toolbar buttons + mouse side buttons), with scroll-position recall.
- Page-turn with
←/→keys;Ctrl+Fredirected to full-book search when hovering the sidebar. - In-place editing: map or topic edits update the page and sidebar without a reload; scroll position and search state survive.
- Per-document persistence of font size, typeface, page width, view mode, and sidebar fold state.
DITA-OT PDF export: comprehensive quality pass
A bundled customization (media/pdf-customization/) now fixes numerous org.dita.pdf2 defaults:
| Issue | Fix |
|---|---|
| Latin text mixed serif/sans-serif | Override common.title and __toc__mini to serif |
| Fullwidth parentheses pushing English into CJK font | Extended zh_CN character set to U+FFEF |
| Page 1 starting at arabic 4+ | Reset startPageNumbering; cover uses roman i |
| Blank page after every odd-page chapter | force-page-count: no-force |
| Bordered "Chapter N" band wraps badly in CJK | Plain single-line label, chapterLayout=BASIC |
navtitle duplicating auto chapter label |
Skip prefix when title already carries it |
| Key-only chapters counted in numbering | Only entries with content get numbered |
| Oversized images overflow the page | Measure + proportional shrink to column/body fit |
<codeph> indistinguishable from prose |
Light background for inline code |
| Backmatter TOC/figure-list uses roman | Arabic when positioned after body |
| Dismissing output dialog silently overwrites | Dismiss now cancels the export |
| Dark-mode white flash on site navigation | Inline <head> script sets theme before paint |
| Top-page links broken for sub-folder maps | Strip stray ../ from index.html hrefs |
Map Checks redesigned for VS Code-native workflow
Validate / Check for Completeness and Find Unreferenced Resources now report findings in the Problems panel instead of a separate webview. The checks and quick-pick UI remain; the Explorer gets a second view no longer. Unreferenced Resources adds a smart folder/type picker with per-workspace memory.
Workspace Trust & security
ditaOtPathandoxygenPathare now machine-scoped — a workspace cannot override them.- DITA-OT: Transform Map…, Open in Oxygen, and Compare with Git Version are gated behind Workspace Trust.
- Virtual workspaces declared unsupported (extension reads local disk).
Git-compare rendered diff
DITA: Compare with Git Version renders both revisions through the normal DITA renderer and diffs the resulting HTML with word-level highlighting, correct HEAD/HEAD~1 resolution, older-on-left ordering, narrow-panel stacking, and recursion into full table/list structures. Chinese-aware similarity scoring included.
Key context map (Oxygen-style "context")
DITA: Select Key Context Map… picks a .ditamap that every keyref resolves against, for multi-brand/product documentation sets. Applies to previews, Go to Definition, Map Navigator, diagnostics, and export; remembered per workspace.
Wrap selection with a DITA tag
In a .dita/.ditamap editor, select text → Enter → pick a tag. The result stays well-formed; nested wrapping by pressing Enter again. Configurable via dita-viewer.wrapSelectionOnEnter.
Map Navigator improvements
- Tree rooted at the map's own
<title>(with keyrefs resolved), Oxygen-style icons per element type. - Submap rows named by the referenced map's title, not its file name.
- Expand All / Collapse All acts on the selected node; state persists across edits and restarts.
- Full-book search in Docsite mode (case/regex toggles, sticky sidebar box, highlight-on-jump).
Other fixes & improvements
conkeyrefwith a fragment on the key's href resolves just that element.- Multiple keys in one
@keysattribute (keys="brand company") each get defined. - Map title given by a
keyrefnow shows the key's value. - In-page search (
Ctrl+F) matches across inline markup and multi-line source; uses CSS Custom Highlight API. - Render error recovers automatically after the source is fixed.
- Unsaved changes in conref targets / keydef maps / book topics reflected in the preview.
- File-size cache invalidation alongside mtime (catches timestamp-preserving edits).
- Profiling highlights: block-level uses real border;
li/ul/olon pseudo-element. - Toolbar buttons consistent height/width; SVG chevrons; Mode button stays in place.
topicheadas collapsible non-clickable group header; resource-only entries excluded from navigation.- Chapter numbering continuous across
mapref/submap boundary. - MathML rendering (MathML Core native,
<mfenced>compat shim). conref/conrefendrange references resolved.indextermchips rendered inline (including inside prolog/metadata/keywords).- Accessibility: ARIA roles/labels on toolbar and outline tree.
Ctrl+K Vopens reading view (matching Markdown'sshowPreviewToSide).- Image lightbox unified across all modes; right-click copy-to-clipboard.
Performance
- Memoized topic and book renders (keyed on actual file dependencies); off-screen book entries skipped.
- Hidden webview panels stop re-rendering.
- One file watcher shared per folder across all DITA features.
- Map tree refreshes on disk change (not only VS Code save).
hrefcompletion no longer blocks the extension host.
Reliability & internal
- Silent
catchblocks now log errors. - Bounded
imageDimensionsCache(LRU) + cleared ondeactivate(). - CI runs
check:l10n; fullsrctypecheck (10 files added). .gitattributespins text to LF.- Deleted hand-written
saxtype shim (shadowed@types/sax). - Full DITA user-manual fixture set under
test-dita-file/manual/. - Patched 5 dev-dependency vulnerabilities;
sharp/libvipsupdated. - Code refactoring: split
ditaRenderUtils.ts(5057→1232 lines) into focused modules; extractedbuildRenderContextshared factory.
Known limitations
See README → Known limitations for a full list with source references. Key items:
- conref/conkeyref chains resolve one hop only.
- CJK spacing does not cover keys inside conref-pulled content.
- External DTDs are stripped; custom domain elements have no specialization.
- Diff renders both sides against the current key map.
- Three commands gated in untrusted workspaces.
- Virtual workspaces unsupported.
Upgrade notes
ditaOtPath/oxygenPath: if you set either in a workspace's.vscode/settings.json, move it to your User settings.- CJK spacing plugin (DITA-OT): the first Transform Map… to PDF/HTML5 after upgrading asks whether to install the bundled plugin into your DITA-OT directory. A timestamped backup is written before any file is modified. Pass
-Ddv.cjk.spacing.mode=offfor a one-off build without it. - Map Checks: the separate Map Checks webview is gone. Findings now appear in the Problems panel (
Ctrl+Shift+M). Use DITA: Clear Map Check Results to dismiss them.
Full Changelog: V1.0.8...V1.0.9
V1.0.8
V1.0.8
This release lands a large batch of preview reliability fixes and rendering improvements — most notably a rebuilt content-update pipeline that stops the preview from full-page reloading on every keystroke — plus MathML rendering, profiling/filtering, richer image handling, and complete English/Chinese localization.
🏗️ Preview Reload Architecture
- The preview no longer reloads the whole page on every source edit. Every edit used to reassign
webview.htmlwholesale — a full page navigation that tore down the DOM, re-decoded every image, and left in-flight scroll/highlight messages racing onto stale or half-loaded content. Edits now post just the freshly-rendered HTML into a dedicated#dita-content-rootdiv; full reloads are reserved for opening a file, a theme switch, and the manual refresh button. Fixes several compounding symptoms this caused: orphaned scroll/highlight animations after a swap, the editor's own auto-reveal fighting the preview's centering, unwanted CSS scroll-anchoring drift, and the editor viewport snapping upward on distant clicks (replaced a fixed ~700ms timing guess with the browser's realscrollendevent). - The same architecture has now been ported to Outline/Book (map) view, which never got this fix — editing a
.ditamappreviously reset scroll to the top and re-decoded every image on every edit, with no restore attempt at all. - Fixed a source ↔ preview scroll-sync feedback loop that could hijack the editor cursor while typing, caused by the preview echoing its own extension-triggered scrolls back out as if they were manual ones.
🧮 MathML
<mathml>formulas now render as real markup via the webview's native MathML Core support, instead of falling back to flattened, unstructured text.- Fixed brackets and absolute-value bars silently vanishing from formulas —
<mfenced>is real MathML 3 (and how Oxygen's equation editor exports every parenthesized/|...|group), but MathML Core dropped it from the spec entirely. It's now expanded at render time into explicit<mo>fence characters.
🏷️ Profiling / Conditional Content
- Profiling attributes (
props,platform,product,audience,otherprops,base,importance,rev,status) are now highlighted with a label naming the matched attribute/value, plus a toolbar Flags toggle and a Filter panel that actually hides content by value — for previewing what a given build/audience would see. - Ditamap/Book view now has its own profiling and filtering, with a topicref's attributes cascading to descendant topicrefs that don't set their own value — matching real
.ditaval/Oxygen conditional-processing semantics. - Fixed ditamap-level profiling display to match Oxygen: one box around the topicref that declares an attribute, enclosing its whole subtree, label shown once — not a repeated chip on every inheriting descendant. Book mode no longer shows topicref-level profiling at all, since it composites topic content for reading the same as opening each topic directly.
- Fixed the highlight box overlapping list bullets/numbers and misaligning nested-list indentation (switched from a wrapping
<span>+borderto attributes applied directly on the element +box-shadow), and the label no longer reserves a fixed empty block below short content.
🖼️ Images
- Alt text,
@scale/@scalefit, and a new zoom/lightbox toolbar — alt text now reads from the<alt>child element or@altattribute;@scaleis honored via CSSzoom; a new per-image −/+/maximize toolbar and click-to-fullscreen lightbox were added. - Fixed the per-image zoom controls shrinking images instead of enlarging them, and the shrink button doing nothing at the default zoom level — added shrink steps below 100% and fixed a closure bug that let zoom on one image affect the wrong baseline.
- Fixed
<img loading="lazy">reserving no space until decoded, causing content below it to jump into place — real width/height is now read from the file header and reserved as an aspect ratio. - Image dimension reads are now cached by file path + modification time — every edit previously re-read every image's file header from disk regardless of whether it had changed; unchanged images now return cached dimensions instantly.
- Images default to left-aligned instead of centered, matching how figures read in Oxygen.
🌐 Localization
- Full English/Simplified Chinese localization of every user-facing string, with automatic detection following the VS Code display language — manifest strings, extension-host strings (notifications, diagnostics, DITA-OT output), webview toolbar/panel strings, and bookmap role badges (第 1 章 / 附录 A / …) all translate through a consistent mechanism. Note-type labels (Warning/警告, …) follow the document's own language instead, matching DITA-OT's own publish-time behavior.
npm run check:l10n— new consistency guard catching missing/untranslated/stale keys across both catalogs.
🔧 Other Fixes
conref+conrefendrange references are now supported — previously only the single element atconrefwas pulled in, silently dropping everything throughconrefend.- Note-type labels (Warning/Attention/…) now cover all 13 DITA 1.3 types (5 previously fell through to an untranslated raw value) and correctly fall back to the editor's display language when a topic has no
xml:langof its own. Added@spectitle/@othertypesupport. - The selected custom CSS theme and page-width dropdown no longer silently revert to default on every edit — both are now persisted per-document.
- Font size and family now persist across preview reopens, with a new reset button.
Full Changelog: V1.0.7...V1.0.8
V1.0.7
V1.0.7
✅ More Complete DITA Element Support
We've significantly expanded the extension's built-in DITA tag mapping to match the official DITA-OT 1.3 DTDs.
This update adds support for many previously uncovered elements across topic types, troubleshooting, glossary structures, task requirements, programming domains, image maps, MathML/SVG content, release management, and bookmap metadata. As a result, these elements now render correctly even when the source XML doesn't explicitly include @class attributes.
This improvement also lays the groundwork for future compatibility, with an automated validation script now included to keep the tag mapping synchronized with future DITA-OT releases.
🧹 <prolog> Metadata No Longer Appears in Preview
Fixed an issue where topic metadata (such as authors, keywords, and revision history) could incorrectly appear as plain text at the beginning of the preview when the source XML omitted an explicit @class attribute on the <prolog> element.
Metadata is now properly recognized and excluded from the document body.
✨ New Highlight Formatting Support
Added support for two additional highlight styles:
- Strikethrough (
line-through) - Overline
These formatting styles now render correctly in the preview and follow the same semantic rendering model as the existing bold, italic, underline, teletype, superscript, and subscript highlights.
V1.0.6
V1.0.6
Authoring Assistance
This release turns DITA Viewer from a reader into a writing companion.
-
✍️ Language features — Ctrl+Click any
keyref/conref/hrefto jump to its definition; context-aware IntelliSense for tags, attributes, defined keys, workspace files, and target ids; HTML-style auto-closing tags (>inserts</tag>,</completes the innermost open tag); broken references flagged in the Problems panel as you type; Outline/breadcrumb support for topics and maps; ready-made snippets for common DITA structures. -
🗂 DITA Map Explorer — the map behind the file you're editing stays visible in the Explorer sidebar, with click-to-open navigation and numbered book divisions.
-
📤 Export as HTML — render any topic or full map to a single shareable
.htmlfile (styles inlined, images embedded) — no DITA-OT required. -
📚 BookMap semantics — chapters, parts, and appendixes now show numbered badges in document order (Chapter 1, Part I, Appendix A…), localized to your VS Code display language (zh-CN: 第 1 章 / 第 I 部分 / 附录 A);
<booktitle>renders as a proper title page with the main title elevated and alternate titles as subtitles. -
🔍 Also included: in-preview search overlay (with regex and case toggles) and improved cross-file
xreftitle resolution.
V1.0.5-hotfix
DITA Viewer 1.0.5 Hotfix
This hotfix addresses a key-resolution gap in the DITA Map preview and slims down the extension package.
🐛 Fixed
keyref now resolves in map titles and navigation names
<ph keyref="..."/> (or any empty element carrying a keyref attribute) inside a map's <title>, navtitle, linktext, or keyword was silently dropped in the map preview — e.g.
<title><ph keyref="product_name"/> User Guide</title>rendered as just "User Guide". The map preview (both Outline and Book mode) now substitutes the resolved key value, so the title renders as "DatabaseX Pro v3.0 User Guide". Element content still wins over the keyref when both are present, and substituted values are HTML-escaped.
📦 Packaging
~60% smaller VSIX — tightened .vscodeignore to exclude development artifacts that were accidentally shipped in 1.0.5 (test build output dist-test/, source maps, CI config, internal docs, test tooling). The package drops from 66 files / 156 KB to 23 files / 62 KB with no functional difference at runtime.
✅ Verification
109 unit tests + 6 end-to-end tests passing.
Full Changelog: https://github.com/aREversez/VS-Code-DITA-Viewer/blob/main/CHANGELOG.md
V1.0.5
V1.0.5
Key Resolution & Map References
- Cross-folder keydef maps:
keyrefvalues now resolve correctly when the key definition map resides in a different folder than the root map (parent, sub-folder, or any reachable location). Resolution now follows the source map's references instead of assuming a same-levelkeys.ditamap. - Href rebasing: When a referenced map is inlined, all local hrefs within it are automatically rebased from the referenced map's folder to the root map's folder. This ensures topics, images, and nested maps resolve correctly regardless of directory depth.
<mapref>support:<mapref>elements are now fully expanded and rendered liketopicref/keydefmap references, replacing the previous empty rendering behavior.- All-in-one maps: Maps that reference only other maps (e.g., sub-maps in nested sub-folders) now render the complete merged tree, including recursively nested map references.
- Key precedence: Key map discovery now scans all ancestor directories (nearest first) using first-definition-wins semantics, aligning with standard DITA key precedence rules.
Map Preview UX
- Refresh button: Added a reload button to the map preview toolbar.
- Mode toggle fix: Fixed an issue where switching to Book mode would get stuck; the toggle now correctly returns to Tree mode on the next click.
- Book mode map entries: Referenced
.ditamapentries now render as section placeholders instead of failing to load as topics.
Reading View ⇄ Source Toggle
- Two-way toggle: Running Open DITA Reading View or Open DITA Map Reading View (via button, keybinding, or command palette) while the reading view is active now switches back to the source editor. This focuses an already-open source tab and closes the preview, or reopens the tab with the text editor in place.
- Context-aware toolbar button: While a reading view is active, the editor title button changes to Back to DITA Source / Back to DITA Map Source (
$(go-to-file)icon), ensuring the tooltip accurately reflects the action. New companion commandsditaViewer.showSourceandditaViewer.showMapSourcehave been added.
Bug Fixes
- Disposed webview guard: Fixed an uncaught "Webview is disposed" error by preventing pending sync timers in the topic preview from posting to a webview that has been closed or replaced.
V1.0.4
V1.0.4
Site-Chrome Enhancements (HTML5/XHTML Output)
- Navigation toolbar — fixed right-side toolbar with section collapse/expand, back-to-home button, and prev/next page navigation (auto-resolved from the DITA map)
- Sidebar TOC — fixed left sidebar with full topic tree; current page highlighted with scroll-into-view; collapses to static on mobile
- On-page heading navigation — floating right-side TOC listing
h2/h3headings within the current page; smooth-scroll on click - Code language labels — language extracted from
outputclass(e.g.language-cpp) and displayed at top-right; clicking the label copies the entire code block content (clipboard API with execCommand fallback) - Back-to-top button — floating circular button at bottom-right, appears after scrolling past 400px
- Dark mode toggle — floating toggle at bottom-right, switches between light/dark themes; respects
prefers-color-schemeon first visit; preference persisted inlocalStorage - Dark mode CSS — comprehensive
dark-mode.csscovering all UI elements (sidebar, toolbar, code blocks, tables, notes, blockquotes, links, inline code/kbd/filepath, related-links) - Index page — homepage detected automatically; sidebar removed; navigation list displayed as centered card-style directory; no toolbar collapse/sidebar/on-page-TOC injected
- All features opt-out via QuickPick during transform flow; injected as
dita-viewer-chrome.js+dita-viewer-chrome.css(+dita-viewer-dark.css) into the DITA-OT output
DITA-OT Transform Workflow
- CSS file selection — QuickPick scans map directory and workspace root for
.cssfiles; selected file passed to DITA-OT viaargs.css/args.cssroot/args.copycss/args.csspath - DITAVAL filter support — optional
.ditavalfile selection passed via--filterfor conditional content profiling - Command rename — "DITA-OT: Transform Map to HTML5" renamed to "DITA-OT: Transform Map…" to reflect multi-transtype support
- Transform icon — toolbar button for the DITA-OT transform command added to the editor title bar
--nav-toc=full— DITA-OT generates full TOC<nav>in every page, enabling the sidebar on topic pages
Bug Fixes
- Drive letter case — Windows drive letters in output paths now consistently normalized to uppercase (prevents "path not found" mismatches)
- Dark mode initial palette — background
#1a1a1a→#1e1e1efor better readability; added missing inline-code, keyboard, filepath, table-alternate-row, and related-links styles - Home button path — now derives correct root-relative path from the injected CSS
<link>href, fixing broken navigation from subdirectory topic pages - Sidebar selectors — changed from
.dv-sidebar ul.map/.dv-sidebar .topicrefto generic.dv-sidebar ul/.dv-sidebar lito correctly match DITA-OT's<nav class="toc">output structure
Infrastructure
esbuild.config.js— production build with minification; development build--devpreserves source mapsditaOtUtils.ts—buildDitaOtArgs()extended with CSS/DITAVAL parameters;SiteChromeFeaturesinterface for feature flag typingextension.ts— full QuickPick-driven transform flow documented with step-by-step progress notification
V1.0.3
V1.0.3
DITA Map Preview
- Add
.ditamapcustom editor (ditaViewer.mapPreview) with structured preview - Tree mode — hierarchical view of map entries (topicref, keydef, topicgroup, etc.) with display name resolution (navtitle → linktext → keyword → href filename → keys)
- Book mode — renders all referenced topics as a continuous reading flow; per-topic
asWebviewUrifor correct image/cross-reference resolution; duplicate-file detection with skip messages - Mode toggle button — switch between Tree and Book modes from the toolbar
- Reltable and topicgroup support — reltables are excluded from the tree; topicgroups contribute children without adding their own entry
Shared Rendering Pipeline
- Extract
renderTopicToHtml(),buildTitleMap(),makeConrefResolver(),makeFileTitleResolver(),detectNoteLabels(),collectText(),escapeHtml(),escapeAttr()intosrc/editor/ditaRenderUtils.ts— pure utilities with novscodedependency, shared between.ditaand.ditamapeditors - Add
renderBookPlaceholder(),renderBookError(),renderBookSkipMessage()for book-mode output
Parser Refactoring
parseXml(xml, tagMap)factory — shared base for both topic and map parsingparseDita(xml)— topic parsing (usesSTANDARD_TAG_TO_BASETYPE)parseDitamap(xml)— map parsing (usesMAP_STANDARD_TAG_TO_BASETYPE)preprocessEntities()moved toditaParser.tssrc/parser/mapTagMap.ts— 20 DITAMAP tags mapped tomap/xxxbaseTypes
Map Renderer
src/render/mapTypeMap.ts—renderMapDocument(),collectMapEntries(),MapEntryinterface,MAP_BASE_TYPE_RENDERERS- Display name priority:
navtitle>linktext>keyword> href filename stripped of extension >keys>"(unnamed)" - All output (titles, hrefs, keys, display names) properly escaped
Keybindings & Commands
- New command
ditaViewer.showMapRenderedwith keybindingCtrl+Shift+Alt+M - New
ditamaplanguage registration withonLanguage:ditamapactivation event - Editor title button and context menu entries for
.ditamapfiles
Security Hardening
resolveTitlereturn value now wrapped inescapeAttr()(prevent XSS in cross-reference titles)- Map title text escaped via
escapeAttr()in map renderer - XSS regression tests for all rendered output: href, keys, title, resolveTitle, placeholder headings, error messages, skip messages
- Content Security Policy:
script-src 'nonce-${nonce}'with nounsafe-inline; toolbar click handlers useaddEventListener
Rendering Enhancements
- CSS theme switcher dropdown in preview toolbar
- Language label on
<codeblock>elements withoutputclass idattribute escaping in rendered HTML- Note type attribute escaping
- Image
altattribute escaping - Ampersand double-escaping prevention in attributes
- Heading level incrementation inside nested
<section>elements <fn>(footnote) and<pre>element support- Improved note rendering with type-specific CSS classes
Bug Fixes
buildKeyMapimport restored after refactoringditaViewer.showMapRenderedcommand registered in extension entry pointparseDitaimport restored in DitaViewerProvider- CI branch filter corrected for pull_request events
V1.0.2
New Features
CSS Theme Switcher — Preview toolbar in the top-right corner with:
- Theme dropdown listing all available
.cssfiles (auto-discovered + configured paths). Switch styles instantly without re-opening the preview. - Page width selector: Auto / Full / Wide (1400px) / Desktop (1280px) / Narrow (720px)
- Refresh button (↻) to reload DITA content
- Toolbar follows VS Code theme (light/dark), semi-transparent when idle
6 example CSS themes for testing custom CSS support:
custom.css— Oxygen WebHelp stylecustom-rose-pine.css— Rosé Pine darkcustom-rose-pine-dawn.css— Rosé Pine lightcustom-doc-corporate.css— Corporate brand style with logo, copyright, full DITA element coveragecustom-doc-modern.css— Modern gradient style with emoji note indicatorscustom-doc-web.css— Full-width browser layout with simulated nav bar and sidebar
Bug Fixes
- Removed custom right-click menu that broke native copy/paste. Context menu now fully preserved.
- Fixed dark mode contrast in corporate theme (headings, links, UI elements now readable)
V1.0.1
v1.0.1 Release Notes
New: Custom CSS Support
- New setting
dita-viewer.customCssto load custom stylesheets that override the default preview appearance - Automatic
custom.cssdiscovery — the extension walks up from the DITA file's directory toward the workspace root and loads the firstcustom.cssfound, zero config required - Supports multiple CSS files; paths can be workspace-relative, document-relative, or absolute
Bug Fixes
- B7:
<fig>images not displayed —renderChildren(c, ctx)was rendering child nodes instead of the element itself; fixed to userenderChildren({ ...node, children: rest }, ctx) - B8: Bidirectional scroll sync bouncing — final solution: instant
scrollIntoView(no animation), forward-only sync,skipVisibleUntil250ms time window, skip redundant scrolls - B9: Scrolling source past the end jumps back to the top — guard: if target > last rendered element +2, scroll to document bottom
- B10: Image load failures —
path.resolvenormalization + dual-load strategy (asWebviewUri → fallback to data URI), CSP allowsdata: