Skip to content

Render bot HTML, images and stable scrolling in the PR reader - #1029

Merged
nortonandreev merged 7 commits into
mainfrom
norton/pr-reader-html-rendering
Sep 21, 2026
Merged

nortonandreev merged 7 commits into
mainfrom
norton/pr-reader-html-rendering

Conversation

@nortonandreev

@nortonandreev nortonandreev commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1007, #1008, #1011 — AI-2943, AI-2944, AI-2947

What & why

Bot comments on GitHub (Qodo, CodeRabbit, Copilot, Dependabot, Renovate) are mostly HTML: nested details, dl/dd, tables, pre, badges and screenshots as img. The reader showed literal tags, alt-text links and boxes in boxes. The GitHub-flavour pass now folds structural tags across blocks with a depth budget, converts tables and the common wrappers/headings/lists/picture, renders only the outermost section as a card and nested ones as an outline, drops wrappers that draw chrome around nothing (rules at an edge, quotes and dds holding only sections — and any dd inside a details body, which Qodo wraps every section in), and fetches images through one https-only, capped, cached loader — the chat flavour still renders none. A bare image is display-only; only an enclosing openable anchor is clickable. A tagged width that exceeds the pane is dropped so the picture shrinks to fit.

Toggling a section jumped the view. The re-render is anchored to the pressed header, but the surviving jump was the reader's scroll bringing the focused viewer into view on press, which moved the header out from under the pointer so the release never toggled it. The reader's scroll no longer follows focus, and the row list is edited in place so a page load or refresh keeps opened sections and loaded images.

Where to look

HtmlBlockFolder (fold with rejection propagation), BlockTidy, MarkdownImages (the one fetch site), MarkdownImage (pane cap and click target), and ReconcileRows in the view model. KcapBorderStrongBrush is a new palette token for a rule that must read across KcapBorderBrush-coloured chrome.

Verification

dotnet run --project test/Capacitor.App.Tests.Unit/Capacitor.App.Tests.Unit.csproj -- --treenode-filter "/*/*/HtmlStructureTests/*" — 23 passed. GitHubMarkdownViewTests — 32 passed. A_press_on_a_section_header_toggles_it_without_scrolling_the_reader reproduces the jump against the old reader (offset moved 93px on press, header stayed checked) and holds at the same offset now.

Visuals

Screenshot 2026-09-21 at 13 25 18 Screenshot 2026-09-21 at 13 13 42 Screenshot 2026-09-21 at 13 13 38 Screenshot 2026-09-21 at 13 13 18

Follow-up

Mermaid fences in the Diagram section still render as code blocks — #1073 / AI-3032.

@nortonandreev nortonandreev self-assigned this Sep 18, 2026
@nortonandreev
nortonandreev marked this pull request as ready for review September 21, 2026 08:11
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Render bot HTML and images with stable PR reader scrolling

✨ Enhancement 🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Converts common bot-authored HTML structures into native GitHub-flavored markdown controls.
• Loads HTTPS images securely with caching, size limits, and pane-aware rendering.
• Preserves section position and row controls across toggles, pagination, and refreshes.
Diagram

graph TD
  A["GitHub Comment"] --> B["HTML Pass"] --> C["Markdown Tree"] --> D["Avalonia Renderers"] --> E["PR Reader"]
  D --> F["Image Loader"] --> G[("Image Cache")]
  H["Row Reconciliation"] --> E
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Embedded WebView rendering
  • ➕ Provides broader browser-level HTML fidelity with less tag-specific conversion logic.
  • ➕ Naturally supports complex nested HTML and intrinsic image layout.
  • ➖ Introduces a much larger security boundary for untrusted bot content.
  • ➖ Complicates native selection, styling, accessibility, link policy, and scroll coordination.
  • ➖ Adds heavyweight runtime and platform-specific behavior to each comment.
2. Dedicated sanitized HTML renderer
  • ➕ Could support more HTML elements without translating them into Markdig nodes.
  • ➕ Separates HTML semantics from the existing markdown parser.
  • ➖ Creates a second layout and selection model beside MarkView.
  • ➖ Still requires strict sanitization, image policy, link handling, and depth limits.
  • ➖ Makes mixed markdown and cross-block HTML structures harder to reconcile.

Recommendation: Keep the PR's AST-conversion approach. It extends the existing Markdig/MarkView pipeline, preserves native selection and styling, and maintains explicit security and depth controls; WebView or parallel HTML rendering would improve raw fidelity but substantially increase security, lifecycle, and interaction complexity.

Files changed (42) +1569 / -137

Enhancement (20) +873 / -70
BlockTidy.csRemove redundant structural chrome +26/-0

Remove redundant structural chrome

• Adds a post-processing pass that unwraps quotes or definition indents containing only details sections and removes edge or duplicate thematic breaks.

src/Capacitor.App/GitHubHtml/BlockTidy.cs

GitHubHtmlPass.csRun generalized HTML folding and cleanup +7/-6

Run generalized HTML folding and cleanup

• Switches container processing to HtmlBlockFolder and invokes BlockTidy before assigning details ordinals.

src/Capacitor.App/GitHubHtml/GitHubHtmlPass.cs

HtmlBlockFolder.csFold structural HTML across markdown blocks +236/-0

Fold structural HTML across markdown blocks

• Generalizes details folding to supported containers such as lists, quotes, definition descriptions, and tables. It validates pairing, parent-child placement, and depth to a rejection fixpoint before atomically rebuilding the document tree.

src/Capacitor.App/GitHubHtml/HtmlBlockFolder.cs

HtmlBlockPartKind.csExpand supported HTML block part kinds +1/-1

Expand supported HTML block part kinds

• Replaces details-specific open and close kinds with generic structural kinds and adds headings and thematic rules.

src/Capacitor.App/GitHubHtml/HtmlBlockPartKind.cs

HtmlBlockReader.csParse common bot HTML into native blocks +55/-16

Parse common bot HTML into native blocks

• Recognizes structural wrappers, headings, rules, transparent formatting wrappers, and cross-block formatting. It emits generalized fold plans while rejecting malformed or unsupported structures safely.

src/Capacitor.App/GitHubHtml/HtmlBlockReader.cs

HtmlContainers.csMap HTML containers to Markdig nodes +82/-0

Map HTML containers to Markdig nodes

• Creates native details, quote, list, indent, and table nodes from HTML tags. It validates required nesting and finalizes table headers, spans, alignments, and column definitions.

src/Capacitor.App/GitHubHtml/HtmlContainers.cs

HtmlIndentBlock.csRepresent HTML definition indentation +8/-0

Represent HTML definition indentation

• Adds a custom container block for dd content that requires indentation without additional visual chrome.

src/Capacitor.App/GitHubHtml/HtmlIndentBlock.cs

HtmlTableCell.csPreserve HTML table cell metadata +10/-0

Preserve HTML table cell metadata

• Adds heading and alignment metadata to Markdig table cells during HTML conversion.

src/Capacitor.App/GitHubHtml/HtmlTableCell.cs

HtmlTags.csRecognize common bot-authored HTML tags +35/-14

Recognize common bot-authored HTML tags

• Expands classification for structural containers, headings, void elements, transparent wrappers, and formatting aliases. Unsupported elements remain source text.

src/Capacitor.App/GitHubHtml/HtmlTags.cs

ImageSize.csPreserve safe image dimensions +28/-0

Preserve safe image dimensions

• Parses bounded pixel width and height attributes and attaches them to image inlines for renderer sizing.

src/Capacitor.App/GitHubHtml/ImageSize.cs

InlineBuilder.csNormalize transparent wrappers and image sizing +6/-0

Normalize transparent wrappers and image sizing

• Carries HTML image dimensions into generated image inlines, ignores non-rendering void tags, and preserves content from transparent wrappers. Trailing line breaks are removed to match browser block behavior.

src/Capacitor.App/GitHubHtml/InlineBuilder.cs

InlinePairing.csExtend inline HTML normalization +14/-5

Extend inline HTML normalization

• Handles non-rendering source and wbr tags, block-only rules, transparent wrappers, and image dimensions when converting inline HTML nodes.

src/Capacitor.App/GitHubHtml/InlinePairing.cs

MarkdownImages.csSecurely fetch and cache markdown images +87/-0

Securely fetch and cache markdown images

• Adds the single HTTPS-only image loader with redirect, timeout, byte, and cache limits. It decodes raster images and detects SVG content from downloaded bytes without forwarding credentials or cookies.

src/Capacitor.App/Services/MarkdownImages.cs

DetailsBlockRenderer.csRender nested details as an outline +24/-4

Render nested details as an outline

• Adds graphical chevrons and distinguishes outer cards from nested header rows. Nested details retain indentation without producing boxes inside boxes.

src/Capacitor.App/Views/DetailsBlockRenderer.cs

HtmlIndentBlockRenderer.csRender definition descriptions with indentation +19/-0

Render definition descriptions with indentation

• Adds an Avalonia renderer for HtmlIndentBlock using a selectable border and stacked child content.

src/Capacitor.App/Views/HtmlIndentBlockRenderer.cs

KcapMarkdownExtension.csRender reader images through native controls +53/-16

Render reader images through native controls

• Registers GitHub-only indent and image-row renderers while leaving chat image behavior unchanged. Images become native controls, and only an enclosing permitted anchor supplies their click target.

src/Capacitor.App/Views/KcapMarkdownExtension.cs

LinkScope.csTrack the outer hyperlink target +9/-2

Track the outer hyperlink target

• Records the outermost active URL so nested image controls can open their enclosing approved anchor without creating nested hyperlinks.

src/Capacitor.App/Views/LinkScope.cs

MarkdownImage.csAdd pane-aware asynchronous image controls +118/-0

Add pane-aware asynchronous image controls

• Adds a label-first image control that loads through MarkdownImages, supports inline and block layout, and caps pictures to the reader pane. Bare images remain display-only while anchored images dispatch through the view's link command.

src/Capacitor.App/Views/MarkdownImage.cs

MarkdownStyles.axamlStyle details outlines, indents, and images +50/-4

Style details outlines, indents, and images

• Aligns details cards and chevrons with chat tool groups, removes nested card chrome, and adds styles for indents, image rows, fallback labels, and marked text.

src/Capacitor.App/Views/MarkdownStyles.axaml

PullRequestStyles.axamlStrengthen and space comment separators +5/-2

Strengthen and space comment separators

• Uses the stronger border token, increases discussion spacing, and removes the separator from the final non-thread comment.

src/Capacitor.App/Views/PullRequestStyles.axaml

Bug fix (3) +38 / -5
PullRequestContextViewModel.Projections.csReconcile visible PR rows in place +12/-2

Reconcile visible PR rows in place

• Uses an ObservableCollection and applies indexed updates instead of replacing the entire row list. Existing controls therefore retain opened details, loaded images, and layout state across paging and refreshes.

src/Capacitor.App/ViewModels/PullRequestContextViewModel.Projections.cs

MarkdownView.csAnchor toggled details headers during layout +25/-2

Anchor toggled details headers during layout

• Captures the pressed header's viewport position and restores it after re-render and settled layout. Focus is retained without allowing expansion or collapse to move the header under the pointer.

src/Capacitor.App/Views/MarkdownView.cs

PullRequestReader.axamlDisable focus-driven reader scrolling +1/-1

Disable focus-driven reader scrolling

• Prevents the reader ScrollViewer from bringing newly focused markdown viewers into view, avoiding pointer-release misses on details headers.

src/Capacitor.App/Views/PullRequestReader.axaml

Refactor (3) +7 / -6
HtmlBlockPart.csCarry structural tags in block plans +3/-2

Carry structural tags in block plans

• Extends planned HTML parts with their associated tag so generalized containers and headings can be created with source attributes.

src/Capacitor.App/GitHubHtml/HtmlBlockPart.cs

HtmlBlockPlan.csTrack all structural tags for matching +2/-2

Track all structural tags for matching

• Replaces details-only tag flags with ordered HtmlToken entries for generalized cross-block structural pairing.

src/Capacitor.App/GitHubHtml/HtmlBlockPlan.cs

HtmlTagClass.csGeneralize structural tag classification +2/-2

Generalize structural tag classification

• Updates the tag-class documentation to reflect multiple cross-block structural elements.

src/Capacitor.App/GitHubHtml/HtmlTagClass.cs

Tests (12) +603 / -56
AvaloniaSession.csDisable real image networking in tests +7/-1

Disable real image networking in tests

• Installs a process-wide null image fetcher before headless Avalonia tests so tests cannot access the network accidentally.

test/Capacitor.App.Tests.Unit/AvaloniaSession.cs

DetailsViewTests.csTest details outlines and stable toggling +61/-2

Test details outlines and stable toggling

• Updates header helpers for the new chevron layout and verifies nested section styling. Adds coverage that toggled headers remain fixed in the viewport.

test/Capacitor.App.Tests.Unit/DetailsViewTests.cs

DetailsFoldTests.csAdapt details folding tests to generalized HTML +6/-6

Adapt details folding tests to generalized HTML

• Uses genuinely unsupported iframe fixtures and adjusts quoted structures to account for redundant-wrapper cleanup.

test/Capacitor.App.Tests.Unit/GitHubHtml/DetailsFoldTests.cs

HtmlBlockRuleTests.csRetain unsupported HTML fallback coverage +2/-2

Retain unsupported HTML fallback coverage

• Replaces now-supported span and div fixtures with iframe cases to continue verifying whole-block rejection and source preservation.

test/Capacitor.App.Tests.Unit/GitHubHtml/HtmlBlockRuleTests.cs

HtmlStructureTests.csCover generalized structural HTML conversion +197/-0

Cover generalized structural HTML conversion

• Adds comprehensive tests for headings, transparent wrappers, lists, quotes, definition lists, tables, rules, malformed nesting, depth limits, and representative bot comments.

test/Capacitor.App.Tests.Unit/GitHubHtml/HtmlStructureTests.cs

HtmlTagsTests.csTest expanded HTML tag classification +28/-3

Test expanded HTML tag classification

• Covers new void, structural, heading, transparent, and formatting tag mappings, including valid heading levels.

test/Capacitor.App.Tests.Unit/GitHubHtml/HtmlTagsTests.cs

InlinePairingTests.csUpdate unsupported inline HTML fixtures +1/-1

Update unsupported inline HTML fixtures

• Uses iframe to verify unknown inline tags remain source after span becomes a supported transparent wrapper.

test/Capacitor.App.Tests.Unit/GitHubHtml/InlinePairingTests.cs

PassTotalityTests.csFuzz the expanded supported HTML surface +4/-1

Fuzz the expanded supported HTML surface

• Adds structural containers, tables, headings, pictures, metadata wrappers, and cross-block formatting to totality test inputs.

test/Capacitor.App.Tests.Unit/GitHubHtml/PassTotalityTests.cs

TreeDump.csDescribe new markdown node shapes in tests +7/-1

Describe new markdown node shapes in tests

• Extends tree serialization for indents, rules, ordered lists, and HTML-derived tables with header and span metadata.

test/Capacitor.App.Tests.Unit/GitHubHtml/TreeDump.cs

GitHubMarkdownViewTests.csTest HTML and image rendering end to end +176/-39

Test HTML and image rendering end to end

• Verifies image loading, SVG detection, fallback labels, sizing, link targets, block rows, tables, headings, rules, and definition indentation. Existing bot-comment assertions now require native images and no literal supported HTML.

test/Capacitor.App.Tests.Unit/GitHubMarkdownViewTests.cs

MarkdownViewHarness.csAdd image interaction test helpers +23/-0

Add image interaction test helpers

• Adds helpers for locating and clicking MarkdownImage controls and temporarily substituting deterministic image bytes while clearing the shared cache.

test/Capacitor.App.Tests.Unit/MarkdownViewHarness.cs

PullRequestPresentationTests.csTest reader state and scrolling stability +91/-0

Test reader state and scrolling stability

• Verifies comment separators, focus-safe details toggling, unchanged reader offsets, and preservation of existing row controls when additional pages load.

test/Capacitor.App.Tests.Unit/PullRequestPresentationTests.cs

Documentation (1) +44 / -0
CHANGES.mdDocument HTML, image, and scrolling behavior +44/-0

Document HTML, image, and scrolling behavior

• Explains structural HTML folding, nested section presentation, secure image loading, pane-aware sizing, row reconciliation, and stable toggling behavior.

docs/CHANGES.md

Other (3) +4 / -0
Directory.Packages.propsPin the Avalonia SVG control package +1/-0

Pin the Avalonia SVG control package

• Adds the centrally managed Svg.Controls.Skia.Avalonia version used to decode and display SVG badges and images.

Directory.Packages.props

App.axamlAdd a stronger border palette token +2/-0

Add a stronger border palette token

• Introduces KcapBorderStrongBrush for separators that must remain visible beside ordinary border-colored chrome.

src/Capacitor.App/App.axaml

Capacitor.App.csprojReference the SVG rendering dependency +1/-0

Reference the SVG rendering dependency

• Adds Svg.Controls.Skia.Avalonia to the desktop application's package references.

src/Capacitor.App/Capacitor.App.csproj

A press focuses the comment's viewer and the reader scrolled it into view on
focus, so the release missed the header: the scroll no longer follows focus, and
the row list is edited in place so a page load keeps opened sections and images.
A bare image is display-only so a divider does not open its CDN URL; a
tagged width that exceeds the pane is dropped so Uniform can shrink it.
@nortonandreev
nortonandreev force-pushed the norton/pr-reader-html-rendering branch from 2844b31 to cfe5aea Compare September 21, 2026 08:12
@qodo-code-review

qodo-code-review Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Image requests can leave HTTPS ✗ Dismissed 🐞 Bug ⛨ Security
Description
MarkdownImages enables automatic redirects after CanLoad validates only the original URL, and
FetchOverHttps never checks the URI reached by the response. An HTTPS image endpoint can redirect
to an HTTP URL, causing pull-request content to be downloaded outside the loader's stated HTTPS-only
boundary.
Code

src/Capacitor.App/Services/MarkdownImages.cs[21]

+        var client = new HttpClient(new SocketsHttpHandler { UseCookies = false, AllowAutoRedirect = true, MaxAutomaticRedirections = 5 }) {
Relevance

●●● Strong

Security boundary violations involving unvalidated external inputs are typically accepted; HTTPS
redirects require destination validation.

PR-#884

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The entry-point check accepts an absolute HTTPS source only, but the configured handler follows
redirects and the fetch path reads the resulting response without rechecking its URI.

src/Capacitor.App/Services/MarkdownImages.cs[20-22]
src/Capacitor.App/Services/MarkdownImages.cs[33-39]
src/Capacitor.App/Services/MarkdownImages.cs[73-85]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The image loader accepts an HTTPS URL but lets `HttpClient` automatically follow redirects without validating the destination scheme, so a remote image can be fetched over HTTP after an HTTPS redirect.

## Fix Focus Areas
- src/Capacitor.App/Services/MarkdownImages.cs[20-22]
- src/Capacitor.App/Services/MarkdownImages.cs[73-85]

## Recommended Fix
Disable automatic redirects and handle redirect responses explicitly, validating each `Location` as an absolute HTTPS URL before issuing the next request (up to the existing redirect limit). Alternatively, validate the final response request URI before reading its content, but ensure no HTTP response body is consumed.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Comments can probe private services ✗ Dismissed 🐞 Bug ⛨ Security
Description
CanLoad accepts every absolute HTTPS URI without rejecting loopback, link-local, or
private-network hosts, and FetchOverHttps sends it through a client with automatic redirects
enabled. When a pull request body contains such an image, displaying the comment makes the
reviewer's machine issue GET requests to services reachable only from its local network, including
redirect destinations.
Code

src/Capacitor.App/Services/MarkdownImages.cs[R33-34]

+    public static bool CanLoad(string? url) =>
+        Uri.TryCreate(url, UriKind.Absolute, out var uri) && uri.Scheme == Uri.UriSchemeHttps;
Relevance

●●● Strong

Private-network access through user-controlled URLs is a concrete SSRF risk, matching recent
accepted input-validation fixes.

PR-#884

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Pull request row bodies are rendered with the GitHub-flavored MarkdownView, whose image controls
pass their source URLs to MarkdownImages. That service validates only the HTTPS scheme before
issuing the request, so addresses such as HTTPS loopback or private hosts satisfy the guard and are
fetched; the shared handler also follows redirects without destination validation.

src/Capacitor.App/Views/PullRequestReader.axaml[151-155]
src/Capacitor.App/Views/KcapMarkdownExtension.cs[47-51]
src/Capacitor.App/Services/MarkdownImages.cs[20-26]
src/Capacitor.App/Services/MarkdownImages.cs[33-39]
src/Capacitor.App/Services/MarkdownImages.cs[73-82]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Pull request image URLs are restricted only by scheme, allowing comment content to initiate requests to loopback, link-local, and private-network services from the reviewer's machine; automatic redirects can also change the destination after validation.

## Fix Focus Areas
- src/Capacitor.App/Services/MarkdownImages.cs[20-26]
- src/Capacitor.App/Services/MarkdownImages.cs[33-39]
- src/Capacitor.App/Services/MarkdownImages.cs[73-85]
- src/Capacitor.App/Views/PullRequestReader.axaml[151-155]

## Recommended Fix
Reject localhost and IP addresses in loopback, link-local, private, multicast, or otherwise non-public ranges, including hostnames that resolve to them. Disable automatic redirects and follow redirects manually only after applying the same scheme and resolved-address validation to every hop, then add tests for direct and redirected private destinations.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Repeated badges download more than once ✗ Dismissed 🐞 Bug ➹ Performance
Description
LoadAsync passes LoadUncachedAsync directly as the ConcurrentDictionary.GetOrAdd value
factory, so concurrent callers can each start a fetch before one resulting task is retained.
Multiple matching image controls attach at once in a rendered comment, making repeated badges or
images issue redundant network requests rather than sharing one outcome per URL.
Code

src/Capacitor.App/Services/MarkdownImages.cs[R38-39]

+        if (Cache.Count >= MaxEntries) Cache.Clear();
+        return Cache.GetOrAdd(url, static u => LoadUncachedAsync(u));
Relevance

●●● Strong

Concurrent async cache factories can duplicate network work; this is a concrete, low-risk
performance correction.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Every attached MarkdownImage starts a load, while the cache invokes an async load factory through
GetOrAdd; concurrent factory invocations can begin fetches independently before a single task wins
the dictionary entry.

src/Capacitor.App/Views/MarkdownImage.cs[64-81]
src/Capacitor.App/Services/MarkdownImages.cs[36-40]
src/Capacitor.App/Services/MarkdownImages.cs[44-48]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Concurrent image controls for the same URL can invoke the `GetOrAdd` value factory more than once, and each invocation begins `LoadUncachedAsync`, so the shared cache does not reliably deduplicate downloads.

## Fix Focus Areas
- src/Capacitor.App/Services/MarkdownImages.cs[18-18]
- src/Capacitor.App/Services/MarkdownImages.cs[36-40]

## Recommended Fix
Store `Lazy<Task<IImage?>>` values (or create the lazy task before insertion) and return the winning lazy value's task, using execution-and-publication semantics so exactly one fetch begins for each cached URL. Keep the cache-cap eviction logic compatible with the new value type.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Earlier-page reloads close open sections ✗ Dismissed 🐞 Bug ≡ Correctness
Description
ReconcileRows aligns rows by array index and replaces every mismatched slot instead of matching
PullRequestRow.Id. ReadPage prepends an evicted page at index zero, so every currently displayed
comment shifts and loses its control-held section and image state when the user reloads earlier
results.
Code

src/Capacitor.App/ViewModels/PullRequestContextViewModel.Projections.cs[73]

+        for (var i = 0; i < rows.Length && i < _visibleRows.Count; i++) if (_visibleRows[i] != rows[i]) _visibleRows[i] = rows[i];
Relevance

●●● Strong

Stable-identity reconciliation bugs are concrete state-preservation issues, consistent with accepted
projection correctness fixes.

PR-#779
PR-#889

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rows are flattened in page order, while earlier pages are explicitly inserted at position zero;
because the new reconciliation loop compares only equal indexes, all previously visible rows become
mismatches. PullRequestRow exposes a stable Id, but the implementation does not use it to
preserve each row's control across that shift.

src/Capacitor.App/ViewModels/PullRequestContextViewModel.Projections.cs[56-58]
src/Capacitor.App/ViewModels/PullRequestContextViewModel.Projections.cs[69-75]
src/Capacitor.App/ViewModels/PullRequestContextViewModel.Reads.cs[113-117]
src/Capacitor.App/ViewModels/PullRequestRow.cs[1-4]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ReconcileRows` reconciles by position, so prepending an earlier page replaces or rebinds all existing rows and discards their control-held state.

## Fix Focus Areas
- src/Capacitor.App/ViewModels/PullRequestContextViewModel.Projections.cs[69-75]
- src/Capacitor.App/ViewModels/PullRequestContextViewModel.Reads.cs[113-117]
- src/Capacitor.App/ViewModels/PullRequestRow.cs[1-4]

## Recommended Fix
Reconcile the observable collection using `PullRequestRow.Id`: insert genuinely new rows at their desired positions, move existing rows when their positions change, remove missing identities, and replace a row only when data for that same identity actually changed. Add coverage for reloading an evicted earlier page and assert that the existing comments retain their `MarkdownView` instances and expanded-section state.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. A list comment narrates old behavior ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
The ReconcileRows comment says that rebuilding every row on page load or refresh lost all of it,
describing behavior from the replaced implementation. Because this historical clause depends on
change context, later readers must reconstruct the implementation history to decide whether it
remains relevant.
Code

src/Capacitor.App/ViewModels/PullRequestContextViewModel.Projections.cs[71]

+    /// contributes — and the list rebuilding every row on a page load or refresh lost all of it.
Relevance

●●● Strong

Recent history shows reviewers accept removing historical or process-oriented narration from
comments.

PR-#703
PR-#666

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Compliance rule 2897915 prohibits change-history narration in comments, and the added comment states
that the former list-rebuilding behavior lost all of it.

Rule 2897915: Avoid time-sensitive or process-reference metadata in code comments
src/Capacitor.App/ViewModels/PullRequestContextViewModel.Projections.cs[69-71]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `ReconcileRows` comment narrates how the replaced list-rebuilding implementation lost control state instead of describing only the current invariant.

## Fix Focus Areas
- src/Capacitor.App/ViewModels/PullRequestContextViewModel.Projections.cs[69-71]

## Recommended Fix
Rewrite the historical clause in present tense as an active invariant, such as saying that replacing the list discards row-held state, without referring to what the previous implementation did.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 64 rules
✅ Cross-repo context — repo relationships
Review mode: 🧠 Deep: This is a large, bug-dense behavioral change spanning HTML parsing, rendering, network image loading, UI interaction/scrolling, state reconciliation, styling, and many independent code paths.

Grey Divider

Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/Capacitor.App/ViewModels/PullRequestContextViewModel.Projections.cs Outdated
Comment thread src/Capacitor.App/Services/MarkdownImages.cs
Comment thread src/Capacitor.App/Services/MarkdownImages.cs
Comment thread src/Capacitor.App/Services/MarkdownImages.cs
@linear-code

linear-code Bot commented Sep 21, 2026

Copy link
Copy Markdown

AI-3031

A details body already insets; leaving each dl/dd adds another 14px per
level and stacks with nested section margins until lists sit mid-pane.
Assessment items already lead with ➕/➖; a bullet beside them and the
emoji on a dark canvas both fight the reader, so those lists become
bold +/- paragraphs and nested bodies keep a lighter inset.
The CI linear-id sweep matches AI-<digits> in test sources; the link
fixture was not ID data, only sample link text.
@nortonandreev
nortonandreev merged commit 4a73b27 into main Sep 21, 2026
8 checks passed
@nortonandreev
nortonandreev deleted the norton/pr-reader-html-rendering branch September 21, 2026 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Render HTML tables in the pull request reader

2 participants