fix(source): retain complete multiline HTML headings when folding

Use parser token spans for raw heading ends, preserving authored multiline attributes, nested title markup and closing syntax. Three newline regressions reproduce the previous header loss; all 589 core tests and required 100% line coverage pass.

Refs #10

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
This commit is contained in:
James MaesandCodex committed 2026-10-11 17:32:31 -05:00
1 parent 764c7ac7fb
commit bd3d72abb8
5 files changed
+88 -6

No files matched your search

+25 -1
View File
@@ -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
+3 -1
View File
@@ -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.
+1 -1
View File
@@ -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,
@@ -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
/// <summary>Tokenizer spans preserve complete raw headings across lines without mistaking quoted attributes or comments for closing syntax.</summary>
private static HtmlParser CreateSourceParser(Dictionary<int, int> 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;
}
}
},
});
}
/// <summary>Source slices retain quote/list indentation offsets while emitted raw HTML omits those Markdown prefixes.</summary>
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<int, int>();
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)
{
@@ -93,6 +93,29 @@ public sealed class DocumentSourceTextTests
/// <summary>A folded multiline HTML heading retains all authored attributes, nested title markup and closing syntax.</summary>
[Theory]
[InlineData("\n")]
[InlineData("\r\n")]
[InlineData("\r")]
public void SetCollapsed_RawMultilineHeading_PreservesCompleteHeader(string newline)
{
string header = string.Join(newline, "<H2", " title='quoted > value'>", "Raw <em>child</em>", "<!-- </h2> is a comment -->", "</H2>");
string original = "# Root" + newline
+ header + newline + "Hidden body" + newline + "<h3>First peer</h3><h3>Second peer</h3>" + 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));
}
/// <summary>Folding accepts only bounded canonical snapshots with matching source anchors.</summary>
[Fact]
public void Create_InvalidSnapshot_RejectsInput()