diff --git a/docs/SESSION-STATE.md b/docs/SESSION-STATE.md index aeb4230..a5dac5d 100644 --- a/docs/SESSION-STATE.md +++ b/docs/SESSION-STATE.md @@ -2,6 +2,30 @@ Updated: 2026-10-11 +## Raw multiline heading fold reproduced and corrected - 2026-10-11 + +Exact764c7ac / CI38179931820 is terminal: focused20/21, native98/99, +published98/99, zero skips. All failures are the newly added raw-HTML case at +its complete-header fold assertion. Published failure prevents the new installed +registration lifecycle readbacks from running; do not claim those passed. +Retain current-focused-764c7ac.log/current-native-764c7ac.log and downloaded TRXs +under artifacts/reader-regressions-38179931820 and +artifacts/current-installed-evidence-38179931820. + +The parser mapped a raw heading's end to its opening line, hiding remaining +multiline attributes/title/closing syntax when folded. Three actual failing core +regressions reproduced this; existing AngleSharp token spans now retain the +complete header, respecting quoted attributes, comments and nested title markup. +All34 source mapping/folding cases and all589 core tests pass with100% measured +line coverage. Release build has zero warnings/errors and format verification +passes. The unchanged raw-HTML runtime case and full distributed suites must +verify the correction on the next pushed head. + +No production publication, signing verification, OS default selection or local +desktop input. Preserve owner preview55472. Actual chooser/Explorer, concurrent +stable/dev isolation, full fidelity audit, genuine maintainer review and final +signed release artifacts remain required. + ## All distributed source cases pass; next release acceptance checks - 2026-10-11 Exact9994eda CI38176727468 is terminal success: focused20/20, native98/98, @@ -10,7 +34,7 @@ Actual AppKit apply/restart advances stable0.1.0→0.4.0 and devbeta.1→beta.2; post-upgrade channel/versions and target DLL hashes are verified by the harness. Retain actual TRXs and before/after JSON under artifacts/current-installed-evidence-38176727468. These fixtures are unsigned and sequential; they do not prove production signing -or simultaneous stable/dev coexistence. Signed cleanupc89ce5c remains local. +or simultaneous stable/dev coexistence. Signed cleanupc89ce5c was pushed as parent of764c7ac. New read-only harness checks require all five Open With/Default Apps extensions at real initial install and after upgrade, the same owned ProgID, complete owned diff --git a/docs/TODO.md b/docs/TODO.md index 60577ea..9345044 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -3,7 +3,9 @@ - [x] Exact6374611: native97/98 and published97/98, focused19/20, zero skips; actual sustained colors/hover/focus and outline pass. - [x] Exact9994eda: offscreen native geometry correction passes focused20/20 and full native98/98; original focus/both-direction glyph/wheel assertions unchanged. - [x] Exact9994eda: published98/98 and installed stable/dev97/97 each, real update/apply/restart and target hashes pass; zero skips. -- [ ] Verify the clean1.0 milestone candidate, actual install/update/uninstall registration readbacks and new raw-HTML runtime case on the next exact head. +- [x] Exact764c7ac: retain focused20/21, native98/99 and published98/99; only the new raw multiline-heading fold fails, zero skips. Installed lifecycle phase is not reached. +- [x] Reproduce multiline raw-header loss in core tests and correct parser token end spans;589 core/100% measured lines, zero-warning Release/format pass. +- [ ] Verify the correction with unchanged raw-HTML runtime assertions and actual install/update/uninstall registration readbacks on the next exact head. - [x] Generate and publish the GUI-only AppKit application with the approved identity. - [x] Configure/read back repository protections and establish the initial hosted CI baseline. diff --git a/docs/standards-readiness.md b/docs/standards-readiness.md index 7c70643..a13feb5 100644 --- a/docs/standards-readiness.md +++ b/docs/standards-readiness.md @@ -10,7 +10,7 @@ The full port and public release are not complete; do not add a conformance badg | Required inventory | `K22-REPO-42` remains pending: signed inventory proposal6b4fd17 is in standards draftPR36; its checks pass. App/starter are Product with adoption-in-progress status. Review/merge is required before claiming registration in main. | | Shared community defaults | `K22-REPO-10` warns: no owner `.github` repository. App governance/security/contributing files exist; shared owner defaults remain optional setup work. | | Workflow reviews | `K22-CI-02/03/31` require review of PR/push tests, documented command alignment and full release gate reuse. Current callers visibly run PR/push(main) CI and the release reuses CI/security. No human review acknowledgement is inferred. | -| Test evidence | Exact9994eda passes586 core tests/100% measured lines, focused20/20 and full native98/98, zero skips. CI38176727468 is terminal: published98/98 and installed stable/dev97/97 each, zero skips; actual apply/restart passes both. Diagnostic cleanup needs fresh exact-head acceptance. | +| Test evidence | Exact9994eda passes586 core tests/100% measured lines, focused20/20 and full native98/98, zero skips. CI38176727468 is terminal: published98/98 and installed stable/dev97/97 each, zero skips; actual apply/restart passes both. Diagnostic-free764c7ac passes98/99 native/published and20/21 focused: the new multiline raw-header fold fails. Local correction passes589 core/100% lines; fresh native/installed registration acceptance remains required. | | Release | Stable/dev synthetic installed updates call AppKit apply/restart successfully. Actual verified Azure identity/profile/signer permissions and exact signed distributed artifacts remain deferred. Owner authorized stable1.0/dev publication after all requirements pass; signing and review requirements remain unmet. | Full acceptance remains in `docs/PRODUCT.md` and `docs/TODO.md`; maintainer review, diff --git a/src/MarkdownViewr.Windows.Core/Documents/DocumentSourceMap.cs b/src/MarkdownViewr.Windows.Core/Documents/DocumentSourceMap.cs index d3a84c2..98a5f86 100644 --- a/src/MarkdownViewr.Windows.Core/Documents/DocumentSourceMap.cs +++ b/src/MarkdownViewr.Windows.Core/Documents/DocumentSourceMap.cs @@ -73,7 +73,7 @@ internal sealed class DocumentSourceMap else if(node is HtmlInline inline && IsHeadingTag(inline.Tag)) { int start = offset + inline.Span.Start; - inline.Tag = inline.Tag.Insert(3, Attribute(AddPosition(start, EndOfLine(start)))); + inline.Tag = inline.Tag.Insert(3, Attribute(AddPosition(start, EndOfLine(offset + inline.Span.End)))); } } return Markdown.ToHtml(document, pipeline); @@ -110,12 +110,41 @@ internal sealed class DocumentSourceMap + /// Tokenizer spans preserve complete raw headings across lines without mistaking quoted attributes or comments for closing syntax. + private static HtmlParser CreateSourceParser(Dictionary headingEnds) + { + int pendingHeading = -1; + return new HtmlParser(new HtmlParserOptions + { + IsKeepingSourceReferences = true, + OnToken = (token, range) => + { + if(token.Name.Length == 2 && token.Name[0] == 'h' && token.Name[1] is >= '1' and <= '6') + { + if(token.Type == HtmlTokenType.StartTag) + { + pendingHeading = range.Start.Index; + headingEnds.Add(pendingHeading, range.End.Index - 1); + } + else if(token.Type == HtmlTokenType.EndTag && pendingHeading >= 0) + { + headingEnds[pendingHeading] = range.End.Index - 1; + pendingHeading = -1; + } + } + }, + }); + } + + + /// Source slices retain quote/list indentation offsets while emitted raw HTML omits those Markdown prefixes. private void AnnotateHtmlBlock(HtmlBlock block, int offset) { StringLine[] lines = block.Lines.Lines; string html = string.Join('\n', lines.Take(block.Lines.Count).Select(line => line.Slice.ToString())); - using AngleSharp.Html.Dom.IHtmlDocument document = new HtmlParser(new HtmlParserOptions { IsKeepingSourceReferences = true }).ParseDocument(html); + var headingEnds = new Dictionary(); + using AngleSharp.Html.Dom.IHtmlDocument document = CreateSourceParser(headingEnds).ParseDocument(html); int[] lineStarts = new int[block.Lines.Count]; int length = 0; for(int index = 0; index < lineStarts.Length; index++) @@ -139,7 +168,11 @@ internal sealed class DocumentSourceMap changes = []; inserts.Add(index, changes); } - changes.Add((column + 3, Attribute(AddPosition(start, EndOfLine(start))))); + int endPosition = headingEnds[position]; + int endIndex = Array.BinarySearch(lineStarts, endPosition); + endIndex = endIndex >= 0 ? endIndex : ~endIndex - 1; + int end = offset + lines[endIndex].Slice.Start + endPosition - lineStarts[endIndex]; + changes.Add((column + 3, Attribute(AddPosition(start, EndOfLine(end))))); } foreach((int index, List<(int Position, string Attribute)> changes) in inserts) { diff --git a/tests/MarkdownViewr.Windows.Core.Tests/DocumentSourceTextTests.cs b/tests/MarkdownViewr.Windows.Core.Tests/DocumentSourceTextTests.cs index 574d994..0b23fe7 100644 --- a/tests/MarkdownViewr.Windows.Core.Tests/DocumentSourceTextTests.cs +++ b/tests/MarkdownViewr.Windows.Core.Tests/DocumentSourceTextTests.cs @@ -93,6 +93,29 @@ public sealed class DocumentSourceTextTests + /// A folded multiline HTML heading retains all authored attributes, nested title markup and closing syntax. + [Theory] + [InlineData("\n")] + [InlineData("\r\n")] + [InlineData("\r")] + public void SetCollapsed_RawMultilineHeading_PreservesCompleteHeader(string newline) + { + string header = string.Join(newline, "", "Raw child", "", ""); + string original = "# Root" + newline + + header + newline + "Hidden body" + newline + "

First peer

Second peer

" + newline + "Peer body" + newline + newline + "# Last" + newline + "Tail"; + var source = new DocumentSourceText(original, new DocumentOutline(original)); + Assert.True(source.SetCollapsed([1])); + Assert.Contains(header.ReplaceLineEndings("\r"), source.Text, StringComparison.Ordinal); + Assert.DoesNotContain("Hidden body", source.Text, StringComparison.Ordinal); + Assert.DoesNotContain("First peer", source.Text, StringComparison.Ordinal); + Assert.Contains("# Last\rTail", source.Text, StringComparison.Ordinal); + Assert.Equal(new[] { 0, 1, 4 }, source.Headings.Select(heading => heading.Index)); + Assert.True(source.SetCollapsed([])); + Assert.Equal(original, source.GetCopyText(0, source.Text.Length)); + } + + + /// Folding accepts only bounded canonical snapshots with matching source anchors. [Fact] public void Create_InvalidSnapshot_RejectsInput()